qless 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/bin/install_phantomjs +7 -0
  2. data/lib/qless.rb +4 -0
  3. data/lib/qless/job.rb +40 -38
  4. data/lib/qless/qless-core/cancel.lua +9 -9
  5. data/lib/qless/qless-core/failed.lua +1 -1
  6. data/lib/qless/qless-core/peek.lua +22 -12
  7. data/lib/qless/qless-core/pop.lua +31 -16
  8. data/lib/qless/qless-core/recur.lua +12 -3
  9. data/lib/qless/server.rb +96 -66
  10. data/lib/qless/server/static/css/bootstrap-responsive.css +686 -0
  11. data/lib/qless/server/static/css/bootstrap-responsive.min.css +12 -0
  12. data/lib/qless/server/static/css/bootstrap.css +3991 -0
  13. data/lib/qless/server/static/css/bootstrap.min.css +689 -0
  14. data/lib/qless/server/static/css/codemirror.css +112 -0
  15. data/lib/qless/server/static/css/docs.css +819 -0
  16. data/lib/qless/server/static/css/jquery.noty.css +105 -0
  17. data/lib/qless/server/static/css/noty_theme_twitter.css +137 -0
  18. data/lib/qless/server/static/css/style.css +204 -0
  19. data/lib/qless/server/static/favicon.ico +0 -0
  20. data/lib/qless/server/static/img/glyphicons-halflings-white.png +0 -0
  21. data/lib/qless/server/static/img/glyphicons-halflings.png +0 -0
  22. data/lib/qless/server/static/js/bootstrap-alert.js +94 -0
  23. data/lib/qless/server/static/js/bootstrap-scrollspy.js +125 -0
  24. data/lib/qless/server/static/js/bootstrap-tab.js +130 -0
  25. data/lib/qless/server/static/js/bootstrap-tooltip.js +270 -0
  26. data/lib/qless/server/static/js/bootstrap-typeahead.js +285 -0
  27. data/lib/qless/server/static/js/bootstrap.js +1726 -0
  28. data/lib/qless/server/static/js/bootstrap.min.js +6 -0
  29. data/lib/qless/server/static/js/codemirror.js +2972 -0
  30. data/lib/qless/server/static/js/jquery.noty.js +220 -0
  31. data/lib/qless/server/static/js/mode/javascript.js +360 -0
  32. data/lib/qless/server/static/js/theme/cobalt.css +18 -0
  33. data/lib/qless/server/static/js/theme/eclipse.css +25 -0
  34. data/lib/qless/server/static/js/theme/elegant.css +10 -0
  35. data/lib/qless/server/static/js/theme/lesser-dark.css +45 -0
  36. data/lib/qless/server/static/js/theme/monokai.css +28 -0
  37. data/lib/qless/server/static/js/theme/neat.css +9 -0
  38. data/lib/qless/server/static/js/theme/night.css +21 -0
  39. data/lib/qless/server/static/js/theme/rubyblue.css +21 -0
  40. data/lib/qless/server/static/js/theme/xq-dark.css +46 -0
  41. data/lib/qless/server/views/_job.erb +219 -0
  42. data/lib/qless/server/views/_job_list.erb +8 -0
  43. data/lib/qless/server/views/_pagination.erb +7 -0
  44. data/lib/qless/server/views/about.erb +130 -0
  45. data/lib/qless/server/views/config.erb +14 -0
  46. data/lib/qless/server/views/failed.erb +48 -0
  47. data/lib/qless/server/views/failed_type.erb +18 -0
  48. data/lib/qless/server/views/job.erb +17 -0
  49. data/lib/qless/server/views/layout.erb +341 -0
  50. data/lib/qless/server/views/overview.erb +90 -0
  51. data/lib/qless/server/views/queue.erb +122 -0
  52. data/lib/qless/server/views/queues.erb +26 -0
  53. data/lib/qless/server/views/tag.erb +6 -0
  54. data/lib/qless/server/views/track.erb +69 -0
  55. data/lib/qless/server/views/worker.erb +34 -0
  56. data/lib/qless/server/views/workers.erb +14 -0
  57. data/lib/qless/version.rb +1 -1
  58. data/lib/qless/worker.rb +11 -2
  59. metadata +72 -6
  60. data/lib/qless/qless-core/ruby/lib/qless-core.rb +0 -1
  61. data/lib/qless/qless-core/ruby/lib/qless/core.rb +0 -13
  62. data/lib/qless/qless-core/ruby/lib/qless/core/version.rb +0 -5
  63. data/lib/qless/qless-core/ruby/spec/qless_core_spec.rb +0 -13
@@ -0,0 +1,8 @@
1
+ <%= erb :_pagination, :layout => false %>
2
+
3
+ <% jobs.each do |job| %>
4
+ <%= erb :_job, :layout => false, :locals => { :job => job, :queues => queues } %>
5
+ <% end %>
6
+
7
+ <%= erb :_pagination, :layout => false %>
8
+
@@ -0,0 +1,7 @@
1
+ <div class="pagination">
2
+ <ul>
3
+ <li><a href="<%= prev_page_url %>">Prev</a></li>
4
+ <li><a href="<%= next_page_url %>">Next</a></li>
5
+ </ul>
6
+ </div>
7
+
@@ -0,0 +1,130 @@
1
+ <div class="subnav subnav-fixed">
2
+ <ul class="nav nav-pills">
3
+ <li><a href="#philosophy">Philosophy</a></li>
4
+ <li><a href="#features">Features</a></li>
5
+ <li><a href="#benchmarks">Benchmarks</a></li>
6
+ </ul>
7
+ </div>
8
+
9
+ <header class="jumbotron subhead" style="margin-top: 40px">
10
+ <h1>Qless <small>In 50 words or less</small></h1>
11
+ <p class="lead">Qless is a <a href="http://redis.io/">Redis</a>-backed, robust, language-agnostic job queueing system.
12
+ It is a collection of <a href="http://github.com/seomoz/qless-core">Lua scripts</a> that run on a Redis server,
13
+ as well as this web app). <a href="http://github.com/seomoz/qless">Qless</a> is open-sourced under the MIT license
14
+ and currently has bindings for <a href="http://github.com/seomoz/qless">Ruby</a>, <a href="http://github.com/seomoz/qless-py">Python</a>
15
+ with C++ and Node.js support coming soon.</p>
16
+ </header>
17
+
18
+ <section id="philosophy">
19
+ <div class="page-header">
20
+ <h1>Philosophy <small>and nomenclature</h1>
21
+ </div>
22
+ <div class="row">
23
+ <div class="span12">
24
+ <p class="lead">A job is a unit of work. A queue can contain several jobs that are scheduled to be run at a certain time, several jobs that are waiting to run, and jobs that are currently running. A worker is a process on a host, identified uniquely, that asks for jobs from the queue, performs some process associated with that job, and then marks it as complete. When it's completed, it can be put into another queue.</p>
25
+ <p class="lead">Jobs can only be in one queue at a time. That queue is whatever queue they were last put in. So if a worker is working on a job, and you move it, the worker's request to complete the job will be ignored.</p>
26
+ <p class="lead">A job can be canceled, which means it disappears into the ether, and we'll never pay it any mind ever again. A job can be dropped, which is when a worker fails to heartbeat or complete the job in a timely fashion, or a job can be failed, which is when a host recognizes some systematically problematic state about the job. A worker should only fail a job if the error is likely not a transient one; otherwise, that worker should just drop it and let the system reclaim it.</p>
27
+ </div>
28
+ </div>
29
+ </section>
30
+
31
+ <section id="features">
32
+ <div class="page-header">
33
+ <h1>Features <small>In no particular order</small></h1>
34
+ </div>
35
+ <div class="row">
36
+ <div class="span12">
37
+ <div class="row">
38
+ <div class="span6">
39
+ <h3 style="text-align:right">Jobs don't get dropped on the floor</h3>
40
+ </div>
41
+ <div class="span6">
42
+ <h3><small>even in the face of the flakiest workers</small></h3>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="row">
47
+ <div class="span6">
48
+ <h3 style="text-align:right">Jobs are stored temporarily</h3>
49
+ </div>
50
+ <div class="span6">
51
+ <h3><small>but automatically expire after a configurable amount of time</small></h3>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="row">
56
+ <div class="span6">
57
+ <h3 style="text-align:right">Tagging and Tracking</h3>
58
+ </div>
59
+ <div class="span6">
60
+ <h3><small>for keeping tabs on important jobs</small></h3>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="row">
65
+ <div class="span6">
66
+ <h3 style="text-align:right">High Performance</h3>
67
+ </div>
68
+ <div class="span6">
69
+ <h3><small>thanks to Redis and Lua</small></h3>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="row">
74
+ <div class="span6">
75
+ <h3 style="text-align:right">Scheduled Work</h3>
76
+ </div>
77
+ <div class="span6">
78
+ <h3><small>for delayed or periodic processing</small></h3>
79
+ </div>
80
+ </div>
81
+
82
+ <div class="row">
83
+ <div class="span6">
84
+ <h3 style="text-align:right">Retry Logic</h3>
85
+ </div>
86
+ <div class="span6">
87
+ <h3><small>for jobs that have been dropped on the floor</small></h3>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="row">
92
+ <div class="span6">
93
+ <h3 style="text-align:right">Web App</h3>
94
+ </div>
95
+ <div class="span6">
96
+ <h3><small>for checking up on progress and monitoring</small></h3>
97
+ </div>
98
+ </div>
99
+
100
+ <div class="row">
101
+ <div class="span6">
102
+ <h3 style="text-align:right">Priority</h3>
103
+ </div>
104
+ <div class="span6">
105
+ <h3><small>for jobs means you can put selected jobs before others</small></h3>
106
+ </div>
107
+ </div>
108
+
109
+ <div class="row">
110
+ <div class="span6">
111
+ <h3 style="text-align:right">Stats</h3>
112
+ </div>
113
+ <div class="span6">
114
+ <h3><small>about how long jobs wait, how long they take to process</small></h3>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </section>
120
+
121
+ <section id="benchmarks">
122
+ <div class="page-header">
123
+ <h1>Benchmarks <small>In Amazon's EC2</small></h1>
124
+ </div>
125
+ <div class="row">
126
+ <div class="span12">
127
+ <p class="lead">These benchmarks were performed on a <span class="badge badge-info">m1.medium</span>, which has 3.75GB of RAM and 2 ECUs. Actually, I'm going to wait until development's a little more complete before posting these.</p>
128
+ </div>
129
+ </div>
130
+ </section>
@@ -0,0 +1,14 @@
1
+ <div class="page-header">
2
+ <h1>Configuration Settings <small>Including defaults</small></h1>
3
+ </div>
4
+
5
+ <% options.each do |key, value| %>
6
+ <div class="row">
7
+ <div class="span4">
8
+ <h2><%= key %></h2>
9
+ </div>
10
+ <div class="span8">
11
+ <h2>=> <%= value %></h2>
12
+ </div>
13
+ </div>
14
+ <% end %>
@@ -0,0 +1,48 @@
1
+ <div class="subnav subnav-fixed">
2
+ <ul class="nav nav-pills">
3
+ <% failed.sort_by { |f| -f['total'] }.each do |f| %>
4
+ <li><a href="#<%= sanitize_attr(f['type']) %>" data-toggle="tab"><%= f['type'] %> (<%= f['total'] %>)</a></li>
5
+ <% end %>
6
+ </ul>
7
+ </div>
8
+
9
+ <div id="alerts" style="margin-top: 40px"></div>
10
+
11
+ <% if failed.empty? %>
12
+ <div class="page-header">
13
+ <h1>No Failed Jobs <small>You Have Done Well!</small></h1>
14
+ </div>
15
+ <% else %>
16
+ <div class="page-header">
17
+ <h1>Failed Jobs <small>Failure is a Part of Success!</small></h1>
18
+ </div>
19
+ <% end %>
20
+
21
+ <div class="tab-content">
22
+ <% failed.sort_by { |f| -f['total'] }.each do |f| %>
23
+ <div class="tab-pane active" id="<%= sanitize_attr(f['type']) %>">
24
+ <div class="page-header">
25
+ <div class="row">
26
+ <div class="span8">
27
+ <h2>
28
+ <a href="<%= u "/failed/#{f['type']}" %>"><%= f['type'] %></a> | <%= f['total'] %> <small>Jobs</small
29
+ </h2>
30
+ </div>
31
+ <div class="span4">
32
+ <div class="btn-group" style="float:right; margin-top: 5px">
33
+ <button class="btn btn-danger" title="cancel" onclick="confirmation(this, 'Cancel?', function() { cancelall('<%= f['type'] %>', fade) })"><i class="icon-remove"></i></button>
34
+ <button class="btn btn-success" title="retry all" onclick="retryall('<%= f['type'] %>', fade)"><i class="icon-repeat"></i></button>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <div class="row">
40
+ <div class="span12">
41
+ <% f['jobs'][0..5].each do |job| %>
42
+ <%= erb :_job, :layout => false, :locals => { :job => job, :queues => queues, :brief => true } %>
43
+ <% end %>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ <% end %>
48
+ </div>
@@ -0,0 +1,18 @@
1
+ <div id="alerts"></div>
2
+
3
+ <div class="page-header">
4
+ <div class="row">
5
+ <div class="span8">
6
+ <h2>Type '<%= type %>' Failed Jobs <small>These jobs are all failed :-/</small></h2>
7
+ </div>
8
+ <div class="span4">
9
+ <div class="btn-group" style="float:right; margin-top: 5px">
10
+ <button class="btn btn-danger" title="cancel" onclick="confirmation(this, 'Cancel?', function() { cancelall('<%= type %>', fade) })"><i class="icon-remove"></i></button>
11
+ <button class="btn btn-success" title="retry all" onclick="retryall('<%= type %>', fade)"><i class="icon-repeat"></i></button>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+
17
+ <%= erb :_job_list, :locals => { :jobs => failed.fetch('jobs'), :queues => queues } %>
18
+
@@ -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,341 @@
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="http://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
+ </style>
54
+
55
+ <script type="text/javascript">
56
+ /* This is a helper method to display an alert at the top of the page,
57
+ * in a vein similar to that of Ruby's flash */
58
+ var flash = function(message, t, duration) {
59
+ var noty_id = noty({
60
+ text : '<strong>' + message + '</strong>',
61
+ layout : 'top',
62
+ type : t || 'error',
63
+ theme : 'noty_theme_twitter',
64
+ timeout: duration || 1500});
65
+ }
66
+
67
+ /* This just sets a few options that we use */
68
+ var _ajax = function(obj) {
69
+ $.ajax({
70
+ url: obj.url,
71
+ type: 'POST',
72
+ dataType: 'json',
73
+ data: JSON.stringify(obj.data),
74
+ processData: false,
75
+ success: obj.success || function() {},
76
+ error: obj.error || function() {}
77
+ });
78
+ }
79
+
80
+ /* This is a helper method to move a job into a queue, and then it will
81
+ * flash a message to that effect on the page */
82
+ var move = function(jid, queue, cb) {
83
+ _ajax({
84
+ url: '<%= u "/move" %>',
85
+ data: {id:jid, queue:queue},
86
+ success: function() { flash('Moved ' + jid + ' to ' + queue, 'success', 1500); cb(jid, queue); },
87
+ erorr: function() { flash('Failed to move ' + jid + ' to ' + queue); }
88
+ });
89
+ }
90
+
91
+ /* Helper function for retrying a job */
92
+ var retry = function(jid, cb) {
93
+ _ajax({
94
+ url: '<%= u "/retry" %>',
95
+ data: {id:jid},
96
+ success: function() { flash('Retrying ' + jid, 'success', 1500); if (cb) { cb(jid, 'retry'); } },
97
+ erorr: function() { flash('Failed to retry ' + jid); }
98
+ });
99
+ }
100
+
101
+ /* This is a helper method to cancel a job */
102
+ var cancel = function(jid, cb) {
103
+ _ajax({
104
+ url: '<%= u "/cancel" %>',
105
+ data: [jid],
106
+ success: function() { flash('Canceled ' + jid, null, 1500); if (cb) { cb(jid, 'cancel'); } },
107
+ error: function() { flash('Failed to cancel ' + jid); }
108
+ });
109
+ }
110
+
111
+ /* This is a helper method to untrack a job */
112
+ var untrack = function(jid, cb) {
113
+ _ajax({
114
+ url: '<%= u "/untrack" %>',
115
+ data: [jid],
116
+ success: function() { flash('Stopped tracking ' + jid, 'success', 1500); if (cb) { cb(jid, 'untrack'); } },
117
+ error: function() { flash('Failed to track ' + jid); }
118
+ });
119
+ }
120
+
121
+ /* This is a helper to start tracking a job, with tags */
122
+ var track = function(jid, tags, cb) {
123
+ _ajax({
124
+ url: '<%= u "/track" %>',
125
+ data: {id:jid, tags:(tags || [])},
126
+ success: function() { flash('Now tracking ' + jid, 'success', 1500); if (cb) { cb(jid, 'track'); } },
127
+ error: function() { flash('Failed to track ' + jid); }
128
+ });
129
+ }
130
+
131
+ /* This is a helper to retry all jobs of a certain failure type */
132
+ var retryall = function(type, cb) {
133
+ _ajax({
134
+ url: '<%= u "/retryall" %>',
135
+ data: {type: type},
136
+ success: function(response) {
137
+ flash('Retrying failures of type ' + type, 'success', 1500);
138
+ if (cb) {
139
+ for (var i in response) {
140
+ cb(response[i].id);
141
+ }
142
+ }
143
+ },
144
+ error: function() { flash('Failed to retry failures of type ' + type); }
145
+ });
146
+ }
147
+
148
+ /* This is a helper to cancel all jobs of a certain failure type */
149
+ var cancelall = function(type, cb) {
150
+ _ajax({
151
+ url: '<%= u "/cancelall" %>',
152
+ data: {type: type},
153
+ success: function(response) {
154
+ flash('Canceling failures of type ' + type, 'success', 1500);
155
+ if (cb) {
156
+ for (var i in response) {
157
+ cb(response[i].id);
158
+ }
159
+ }
160
+ },
161
+ error: function() { flash('Failed to cancel failures of type ' + type); }
162
+ });
163
+ }
164
+
165
+ /* This is a helper to remove job dependencies */
166
+ var undepend = function(jid, dependency, cb) {
167
+ _ajax({
168
+ url: '<%= u "/undepend" %>',
169
+ data: {id:jid, dependency:dependency},
170
+ success: function() { flash(jid + ' no longer depends on ' + dependency, 'success', 1500); if (cb) { cb(jid, 'undepend'); } },
171
+ error: function() { flash('Failed to remove ' + jid + '\'s dependency on ' + dependency); }
172
+ });
173
+ }
174
+
175
+ /* Helper function to fade out a particular element id */
176
+ var fade = function(jid, type) {
177
+ if (type != 'untrack' && type != 'track') {
178
+ $('#job-' + jid).slideUp();
179
+ }
180
+ }
181
+
182
+ /* Helper function to make a button ask for confirmation
183
+ * after being pressed once. Accepts the confirmation text,
184
+ * and the function to execute after it has been run */
185
+ var confirmation = function(button, html, action, delay) {
186
+ var obj = $(button);
187
+ var original = obj.html();
188
+ var timeout = setTimeout(function() {
189
+ obj.html(original).unbind('click').click(function() {
190
+ confirmation(button, html, action, delay);
191
+ });
192
+ }, delay || 3000);
193
+ obj.removeAttr('onclick').html(html).unbind('click').click(function() {
194
+ clearTimeout(timeout);
195
+ obj.html(original).unbind('click').click(function() {
196
+ confirmation(button, html, action, delay);
197
+ });
198
+ action();
199
+ });
200
+ }
201
+
202
+ /* Helper function for adding a tag to a job */
203
+ var tag = function(jid, tag) {
204
+ var data = {};
205
+ data[jid] = [tag];
206
+ // The button group of the 'add tag' bit
207
+ var group =
208
+ _ajax({
209
+ url: '<%= u "/tag" %>',
210
+ data: data,
211
+ success : function() {
212
+ var div = $('<div>').attr('class', 'btn-group').attr('style', 'float:left');
213
+ var span = $('<span>').attr('class', 'tag').text(tag);
214
+ var btn = $('<button>').attr('class', 'btn').click(function() {
215
+ untag(jid, tag);
216
+ });
217
+ btn.append($('<i>').attr('class', 'icon-remove'));
218
+ div.append(span).append(btn);
219
+ $('#job-' + jid).find('.add-tag').val(null).parent().before(div);
220
+ }, error: function() {
221
+ flash('Failed to tag ' + jid + ' with ' + tag);
222
+ }
223
+ });
224
+ }
225
+
226
+ /* Helper function for untagging a job */
227
+ var untag = function(jid, tag) {
228
+ var data = {};
229
+ data[jid] = [tag];
230
+ // The button group of the 'add tag' bit
231
+ var group =
232
+ _ajax({
233
+ url: '<%= u "/untag" %>',
234
+ data: data,
235
+ success : function() {
236
+ $("#job-" + jid).find('.tag').filter(function() {
237
+ return $(this).text() === tag;
238
+ }).parent().remove();
239
+ }, error: function() {
240
+ flash('Failed to untag ' + jid + ' with ' + tag);
241
+ }
242
+ });
243
+ }
244
+
245
+ /* Helper function for changing a job's priority */
246
+ var priority = function(jid, priority) {
247
+ var p = parseInt(priority);
248
+ var input = $('#job-' + jid).find('.priority');
249
+ if (p != null) {
250
+ input.attr('disabled', true);
251
+ var data = {};
252
+ data[jid] = priority;
253
+ _ajax({
254
+ url: '<%= u "/priority" %>',
255
+ data: data,
256
+ success : function(data) {
257
+ if (data[jid] != 'failed' && data[jid] != null) {
258
+ input.attr('disabled', false).attr('placeholder', 'Pri ' + priority).val(null).blur();
259
+ } else {
260
+ flash('Couldn\'t reprioritize ' + jid);
261
+ input.val(null).blur();
262
+ }
263
+ }, error: function() {
264
+ flash('Couldn\'t reprioritize ' + jid);
265
+ input.val(null).blur();
266
+ }
267
+ });
268
+ } else {
269
+ // Reset it to its original value, and print an error
270
+ flash('Cannot derive integer from "' + priority + '"');
271
+ input.val(null).blur();
272
+ }
273
+ }
274
+
275
+ $(document).ready(function() {
276
+ $('button').tooltip({delay:200});
277
+ });
278
+ </script>
279
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
280
+ <!--[if lt IE 9]>
281
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
282
+ <![endif]-->
283
+ </head>
284
+
285
+ <body>
286
+ <div class="navbar navbar-fixed-top">
287
+ <div class="navbar-inner">
288
+ <div class="container">
289
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
290
+ <span class="icon-bar"></span>
291
+ <span class="icon-bar"></span>
292
+ <span class="icon-bar"></span>
293
+ </a>
294
+ <a class="brand" href="<%= u '/' %>"><%= application_name %></a>
295
+ <div class="nav-collapse">
296
+ <ul class="nav nav-bar">
297
+ <% tabs.each do |tab| %>
298
+ <li><a href='<%= u tab[:path] %>'><%= tab[:name] %></a></li>
299
+ <% end %>
300
+ </ul>
301
+ <ul class="nav nav-bar pull-right">
302
+ <li>
303
+ <form class="navbar-search" action="<%= u '/tag' %>">
304
+ <input id="tag-search" type="text" class="search-query" placeholder="Search by Tag" data-provide="typeahead" name="tag"/>
305
+ </form>
306
+ </li>
307
+ </ul>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ </div>
312
+
313
+ <div class="container">
314
+ <%= yield %>
315
+ <!-- <footer class="footer">
316
+ <p>Powered by <a href="http://github.com/seomoz/qless">qless</a> v<%=Qless::VERSION%></p>
317
+ </footer> -->
318
+ </div> <!-- /container -->
319
+
320
+ <!-- Le javascript
321
+ ================================================== -->
322
+ <script src="<%= u '/js/bootstrap.min.js' %>"></script>
323
+ <script src="<%= u '/js/bootstrap-tab.js' %>"></script>
324
+ <script src="<%= u '/js/bootstrap-alert.js' %>"></script>
325
+ <script src="<%= u '/js/bootstrap-tooltip.js' %>"></script>
326
+ <script src="<%= u '/js/bootstrap-scrollspy.js' %>"></script>
327
+ <script src="<%= u '/js/bootstrap-typeahead.js' %>"></script>
328
+
329
+ <!-- Noty! This is such a wonderful-looking library, and /exactly/ what I wanted. Thank you so much! -->
330
+ <script src="<%= u '/js/jquery.noty.js' %>"></script>
331
+ <!--
332
+ <script src="../assets/js/bootstrap-transition.js"></script>
333
+ <script src="../assets/js/bootstrap-modal.js"></script>
334
+ <script src="../assets/js/bootstrap-dropdown.js"></script>
335
+ <script src="../assets/js/bootstrap-popover.js"></script>
336
+ <script src="../assets/js/bootstrap-button.js"></script>
337
+ <script src="../assets/js/bootstrap-collapse.js"></script>
338
+ <script src="../assets/js/bootstrap-carousel.js"></script>
339
+ -->
340
+ </body>
341
+ </html>