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
@@ -34,6 +34,15 @@ if command == 'on' then
34
34
  options.tags = assert(cjson.decode(options.tags or {}), 'Recur(): Arg "tags" must be JSON-encoded array of string. Got: ' .. tostring(options.tags))
35
35
  options.priority = assert(tonumber(options.priority or 0) , 'Recur(): Arg "priority" must be a number. Got: ' .. tostring(options.priority))
36
36
  options.retries = assert(tonumber(options.retries or 0) , 'Recur(): Arg "retries" must be a number. Got: ' .. tostring(options.retries))
37
+
38
+ local count, old_queue = unpack(redis.call('hmget', 'ql:r:' .. jid, 'count', 'queue'))
39
+ count = count or 0
40
+
41
+ -- If it has previously been in another queue, then we should remove
42
+ -- some information about it
43
+ if old_queue then
44
+ redis.call('zrem', 'ql:q:' .. old_queue .. '-recur', jid)
45
+ end
37
46
 
38
47
  -- Do some insertions
39
48
  redis.call('hmset', 'ql:r:' .. jid,
@@ -46,7 +55,7 @@ if command == 'on' then
46
55
  'queue' , queue,
47
56
  'type' , 'interval',
48
57
  -- How many jobs we've spawned from this
49
- 'count' , 0,
58
+ 'count' , count,
50
59
  'interval', interval,
51
60
  'retries' , options.retries)
52
61
  -- Now, we should schedule the next run of the job
@@ -102,9 +111,9 @@ elseif command == 'update' then
102
111
  local options = {}
103
112
 
104
113
  -- Make sure that the job exists
105
- if redis.call('exists', 'ql:r:' .. jid) then
114
+ if redis.call('exists', 'ql:r:' .. jid) ~= 0 then
106
115
  for i = 3, #ARGV, 2 do
107
- local key = ARGV[i]
116
+ local key = ARGV[i]
108
117
  local value = ARGV[i+1]
109
118
  if key == 'priority' or key == 'interval' or key == 'retries' then
110
119
  value = assert(tonumber(value), 'Recur(): Arg "' .. key .. '" must be a number: ' .. tostring(value))
@@ -9,13 +9,13 @@ module Qless
9
9
  dir = File.dirname(File.expand_path(__FILE__))
10
10
  set :views , "#{dir}/server/views"
11
11
  set :public_folder, "#{dir}/server/static"
12
-
12
+
13
13
  # For debugging purposes at least, I want this
14
14
  set :reload_templates, true
15
-
15
+
16
16
  # I'm not sure what this option is -- I'll look it up later
17
17
  # set :static, true
18
-
18
+
19
19
  def self.client
20
20
  @client ||= Qless::Client.new
21
21
  end
@@ -23,7 +23,7 @@ module Qless
23
23
  def self.client=(client)
24
24
  @client = client
25
25
  end
26
-
26
+
27
27
  helpers do
28
28
  include Rack::Utils
29
29
 
@@ -35,7 +35,46 @@ module Qless
35
35
  def path_prefix
36
36
  request.env['SCRIPT_NAME']
37
37
  end
38
-
38
+
39
+ def url_with_modified_query
40
+ url = URI(request.url)
41
+ existing_query = Rack::Utils.parse_query(url.query)
42
+ url.query = Rack::Utils.build_query(yield existing_query)
43
+ url.to_s
44
+ end
45
+
46
+ def page_url(offset)
47
+ url_with_modified_query do |query|
48
+ query.merge('page' => current_page + offset)
49
+ end
50
+ end
51
+
52
+ def next_page_url
53
+ page_url 1
54
+ end
55
+
56
+ def prev_page_url
57
+ page_url -1
58
+ end
59
+
60
+ def current_page
61
+ @current_page ||= begin
62
+ Integer(params[:page])
63
+ rescue
64
+ 1
65
+ end
66
+ end
67
+
68
+ PAGE_SIZE = 25
69
+ def pagination_values
70
+ start = (current_page - 1) * PAGE_SIZE
71
+ [start, start + PAGE_SIZE]
72
+ end
73
+
74
+ def paginated(qless_object, method, *args)
75
+ qless_object.send(method, *(args + pagination_values))
76
+ end
77
+
39
78
  def tabs
40
79
  return [
41
80
  {:name => 'Queues' , :path => '/queues' },
@@ -46,38 +85,38 @@ module Qless
46
85
  {:name => 'About' , :path => '/about' }
47
86
  ]
48
87
  end
49
-
88
+
50
89
  def application_name
51
90
  return Server.client.config['application']
52
91
  end
53
-
92
+
54
93
  def queues
55
94
  return Server.client.queues.counts
56
95
  end
57
-
96
+
58
97
  def tracked
59
98
  return Server.client.jobs.tracked
60
99
  end
61
-
100
+
62
101
  def workers
63
102
  return Server.client.workers.counts
64
103
  end
65
-
104
+
66
105
  def failed
67
106
  return Server.client.jobs.failed
68
107
  end
69
-
108
+
70
109
  # Return the supplied object back as JSON
71
110
  def json(obj)
72
111
  content_type :json
73
112
  obj.to_json
74
113
  end
75
-
114
+
76
115
  # Make the id acceptable as an id / att in HTML
77
116
  def sanitize_attr(attr)
78
117
  return attr.gsub(/[^a-zA-Z\:\_]/, '-')
79
118
  end
80
-
119
+
81
120
  # What are the top tags? Since it might go on, say, every
82
121
  # page, then we should probably be caching it
83
122
  def top_tags
@@ -93,7 +132,7 @@ module Qless
93
132
  end
94
133
  @top_tags[:top]
95
134
  end
96
-
135
+
97
136
  def strftime(t)
98
137
  # From http://stackoverflow.com/questions/195740/how-do-you-do-relative-time-in-rails
99
138
  diff_seconds = Time.now - t
@@ -104,54 +143,47 @@ module Qless
104
143
  "#{(diff_seconds/60).to_i} minutes ago"
105
144
  when 3600 ... 3600*24
106
145
  "#{(diff_seconds/3600).to_i} hours ago"
107
- when (3600*24) ... (3600*24*30)
146
+ when (3600*24) ... (3600*24*30)
108
147
  "#{(diff_seconds/(3600*24)).to_i} days ago"
109
148
  else
110
149
  t.strftime('%b %e, %Y %H:%M:%S %Z (%z)')
111
150
  end
112
151
  end
113
152
  end
114
-
153
+
115
154
  get '/?' do
116
155
  erb :overview, :layout => true, :locals => { :title => "Overview" }
117
156
  end
118
-
157
+
119
158
  # Returns a JSON blob with the job counts for various queues
120
159
  get '/queues.json' do
121
160
  json(Server.client.queues.counts)
122
161
  end
123
-
162
+
124
163
  get '/queues/?' do
125
164
  erb :queues, :layout => true, :locals => {
126
165
  :title => 'Queues'
127
166
  }
128
167
  end
129
-
168
+
130
169
  # Return the job counts for a specific queue
131
170
  get '/queues/:name.json' do
132
171
  json(Server.client.queues[params[:name]].counts)
133
172
  end
134
-
173
+
174
+ filtered_tabs = %w[ running scheduled stalled depends recurring ].to_set
135
175
  get '/queues/:name/?:tab?' do
136
176
  queue = Server.client.queues[params[:name]]
137
- tab = params.fetch('tab', 'stats')
138
- jobs = []
139
- case tab
140
- when 'running'
141
- jobs = queue.jobs.running
142
- when 'scheduled'
143
- jobs = queue.jobs.scheduled
144
- when 'stalled'
145
- jobs = queue.jobs.stalled
146
- when 'depends'
147
- jobs = queue.jobs.depends
148
- when 'recurring'
149
- jobs = queue.jobs.recurring
150
- end
151
- jobs = jobs.map { |jid| Server.client.jobs[jid] }
152
- if tab == 'waiting'
153
- jobs = queue.peek(20)
177
+ tab = params.fetch('tab', 'stats')
178
+
179
+ jobs = if tab == 'waiting'
180
+ queue.peek(20)
181
+ elsif filtered_tabs.include?(tab)
182
+ paginated(queue.jobs, tab).map { |jid| Server.client.jobs[jid] }
183
+ else
184
+ []
154
185
  end
186
+
155
187
  erb :queue, :layout => true, :locals => {
156
188
  :title => "Queue #{params[:name]}",
157
189
  :tab => tab,
@@ -160,7 +192,11 @@ module Qless
160
192
  :stats => queue.stats
161
193
  }
162
194
  end
163
-
195
+
196
+ get '/failed.json' do
197
+ json(Server.client.jobs.failed)
198
+ end
199
+
164
200
  get '/failed/?' do
165
201
  # qless-core doesn't provide functionality this way, so we'll
166
202
  # do it ourselves. I'm not sure if this is how the core library
@@ -170,21 +206,21 @@ module Qless
170
206
  :failed => Server.client.jobs.failed.keys.map { |t| Server.client.jobs.failed(t).tap { |f| f['type'] = t } }
171
207
  }
172
208
  end
173
-
209
+
174
210
  get '/failed/:type/?' do
175
211
  erb :failed_type, :layout => true, :locals => {
176
212
  :title => 'Failed | ' + params[:type],
177
213
  :type => params[:type],
178
- :failed => Server.client.jobs.failed(params[:type])
214
+ :failed => paginated(Server.client.jobs, :failed, params[:type])
179
215
  }
180
216
  end
181
-
217
+
182
218
  get '/track/?' do
183
219
  erb :track, :layout => true, :locals => {
184
220
  :title => 'Track'
185
221
  }
186
222
  end
187
-
223
+
188
224
  get '/jobs/:jid' do
189
225
  erb :job, :layout => true, :locals => {
190
226
  :title => "Job | #{params[:jid]}",
@@ -192,7 +228,7 @@ module Qless
192
228
  :job => Server.client.jobs[params[:jid]]
193
229
  }
194
230
  end
195
-
231
+
196
232
  get '/workers/?' do
197
233
  erb :workers, :layout => true, :locals => {
198
234
  :title => 'Workers'
@@ -209,9 +245,9 @@ module Qless
209
245
  }
210
246
  }
211
247
  end
212
-
248
+
213
249
  get '/tag/?' do
214
- jobs = Server.client.jobs.tagged(params[:tag])
250
+ jobs = paginated(Server.client.jobs, :tagged, params[:tag])
215
251
  erb :tag, :layout => true, :locals => {
216
252
  :title => "Tag | #{params[:tag]}",
217
253
  :tag => params[:tag],
@@ -219,26 +255,20 @@ module Qless
219
255
  :total => jobs['total']
220
256
  }
221
257
  end
222
-
258
+
223
259
  get '/config/?' do
224
260
  erb :config, :layout => true, :locals => {
225
261
  :title => 'Config',
226
262
  :options => Server.client.config.all
227
263
  }
228
264
  end
229
-
265
+
230
266
  get '/about/?' do
231
267
  erb :about, :layout => true, :locals => {
232
268
  :title => 'About'
233
269
  }
234
270
  end
235
-
236
-
237
-
238
-
239
-
240
-
241
-
271
+
242
272
  # These are the bits where we accept AJAX requests
243
273
  post "/track/?" do
244
274
  # Expects a JSON-encoded hash with a job id, and optionally some tags
@@ -259,7 +289,7 @@ module Qless
259
289
  end
260
290
  end
261
291
  end
262
-
292
+
263
293
  post "/untrack/?" do
264
294
  # Expects a JSON-encoded array of job ids to stop tracking
265
295
  jobs = JSON.parse(request.body.read).map { |jid| Server.client.jobs[jid] }.select { |j| not j.nil? }
@@ -269,7 +299,7 @@ module Qless
269
299
  end
270
300
  return json({ :untracked => jobs.map { |job| job.jid } })
271
301
  end
272
-
302
+
273
303
  post "/priority/?" do
274
304
  # Expects a JSON-encoded dictionary of jid => priority
275
305
  response = Hash.new
@@ -284,7 +314,7 @@ module Qless
284
314
  end
285
315
  return json(response)
286
316
  end
287
-
317
+
288
318
  post "/tag/?" do
289
319
  # Expects a JSON-encoded dictionary of jid => [tag, tag, tag]
290
320
  response = Hash.new
@@ -298,7 +328,7 @@ module Qless
298
328
  end
299
329
  return json(response)
300
330
  end
301
-
331
+
302
332
  post "/untag/?" do
303
333
  # Expects a JSON-encoded dictionary of jid => [tag, tag, tag]
304
334
  response = Hash.new
@@ -312,7 +342,7 @@ module Qless
312
342
  end
313
343
  return json(response)
314
344
  end
315
-
345
+
316
346
  post "/move/?" do
317
347
  # Expects a JSON-encoded hash of id: jid, and queue: queue_name
318
348
  data = JSON.parse(request.body.read)
@@ -328,7 +358,7 @@ module Qless
328
358
  end
329
359
  end
330
360
  end
331
-
361
+
332
362
  post "/undepend/?" do
333
363
  # Expects a JSON-encoded hash of id: jid, and queue: queue_name
334
364
  data = JSON.parse(request.body.read)
@@ -344,7 +374,7 @@ module Qless
344
374
  end
345
375
  end
346
376
  end
347
-
377
+
348
378
  post "/retry/?" do
349
379
  # Expects a JSON-encoded hash of id: jid, and queue: queue_name
350
380
  data = JSON.parse(request.body.read)
@@ -361,7 +391,7 @@ module Qless
361
391
  end
362
392
  end
363
393
  end
364
-
394
+
365
395
  # Retry all the failures of a particular type
366
396
  post "/retryall/?" do
367
397
  # Expects a JSON-encoded hash of type: failure-type
@@ -376,7 +406,7 @@ module Qless
376
406
  end)
377
407
  end
378
408
  end
379
-
409
+
380
410
  post "/cancel/?" do
381
411
  # Expects a JSON-encoded array of job ids to cancel
382
412
  jobs = JSON.parse(request.body.read).map { |jid| Server.client.jobs[jid] }.select { |j| not j.nil? }
@@ -384,14 +414,14 @@ module Qless
384
414
  jobs.each do |job|
385
415
  job.cancel()
386
416
  end
387
-
417
+
388
418
  if request.xhr?
389
419
  return json({ :canceled => jobs.map { |job| job.jid } })
390
420
  else
391
421
  redirect to(request.referrer)
392
422
  end
393
423
  end
394
-
424
+
395
425
  post "/cancelall/?" do
396
426
  # Expects a JSON-encoded hash of type: failure-type
397
427
  data = JSON.parse(request.body.read)
@@ -404,7 +434,7 @@ module Qless
404
434
  end)
405
435
  end
406
436
  end
407
-
437
+
408
438
  # start the server if ruby file executed directly
409
439
  run! if app_file == $0
410
440
  end
@@ -0,0 +1,686 @@
1
+ /*!
2
+ * Bootstrap Responsive v2.0.2
3
+ *
4
+ * Copyright 2012 Twitter, Inc
5
+ * Licensed under the Apache License v2.0
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
9
+ */
10
+ .clearfix {
11
+ *zoom: 1;
12
+ }
13
+ .clearfix:before,
14
+ .clearfix:after {
15
+ display: table;
16
+ content: "";
17
+ }
18
+ .clearfix:after {
19
+ clear: both;
20
+ }
21
+ .hide-text {
22
+ overflow: hidden;
23
+ text-indent: 100%;
24
+ white-space: nowrap;
25
+ }
26
+ .input-block-level {
27
+ display: block;
28
+ width: 100%;
29
+ min-height: 28px;
30
+ /* Make inputs at least the height of their button counterpart */
31
+
32
+ /* Makes inputs behave like true block-level elements */
33
+
34
+ -webkit-box-sizing: border-box;
35
+ -moz-box-sizing: border-box;
36
+ -ms-box-sizing: border-box;
37
+ box-sizing: border-box;
38
+ }
39
+ .hidden {
40
+ display: none;
41
+ visibility: hidden;
42
+ }
43
+ .visible-phone {
44
+ display: none;
45
+ }
46
+ .visible-tablet {
47
+ display: none;
48
+ }
49
+ .visible-desktop {
50
+ display: block;
51
+ }
52
+ .hidden-phone {
53
+ display: block;
54
+ }
55
+ .hidden-tablet {
56
+ display: block;
57
+ }
58
+ .hidden-desktop {
59
+ display: none;
60
+ }
61
+ @media (max-width: 767px) {
62
+ .visible-phone {
63
+ display: block;
64
+ }
65
+ .hidden-phone {
66
+ display: none;
67
+ }
68
+ .hidden-desktop {
69
+ display: block;
70
+ }
71
+ .visible-desktop {
72
+ display: none;
73
+ }
74
+ }
75
+ @media (min-width: 768px) and (max-width: 979px) {
76
+ .visible-tablet {
77
+ display: block;
78
+ }
79
+ .hidden-tablet {
80
+ display: none;
81
+ }
82
+ .hidden-desktop {
83
+ display: block;
84
+ }
85
+ .visible-desktop {
86
+ display: none;
87
+ }
88
+ }
89
+ @media (max-width: 480px) {
90
+ .nav-collapse {
91
+ -webkit-transform: translate3d(0, 0, 0);
92
+ }
93
+ .page-header h1 small {
94
+ display: block;
95
+ line-height: 18px;
96
+ }
97
+ input[type="checkbox"],
98
+ input[type="radio"] {
99
+ border: 1px solid #ccc;
100
+ }
101
+ .form-horizontal .control-group > label {
102
+ float: none;
103
+ width: auto;
104
+ padding-top: 0;
105
+ text-align: left;
106
+ }
107
+ .form-horizontal .controls {
108
+ margin-left: 0;
109
+ }
110
+ .form-horizontal .control-list {
111
+ padding-top: 0;
112
+ }
113
+ .form-horizontal .form-actions {
114
+ padding-left: 10px;
115
+ padding-right: 10px;
116
+ }
117
+ .modal {
118
+ position: absolute;
119
+ top: 10px;
120
+ left: 10px;
121
+ right: 10px;
122
+ width: auto;
123
+ margin: 0;
124
+ }
125
+ .modal.fade.in {
126
+ top: auto;
127
+ }
128
+ .modal-header .close {
129
+ padding: 10px;
130
+ margin: -10px;
131
+ }
132
+ .carousel-caption {
133
+ position: static;
134
+ }
135
+ }
136
+ @media (max-width: 767px) {
137
+ body {
138
+ padding-left: 20px;
139
+ padding-right: 20px;
140
+ }
141
+ .navbar-fixed-top {
142
+ margin-left: -20px;
143
+ margin-right: -20px;
144
+ }
145
+ .container {
146
+ width: auto;
147
+ }
148
+ .row-fluid {
149
+ width: 100%;
150
+ }
151
+ .row {
152
+ margin-left: 0;
153
+ }
154
+ .row > [class*="span"],
155
+ .row-fluid > [class*="span"] {
156
+ float: none;
157
+ display: block;
158
+ width: auto;
159
+ margin: 0;
160
+ }
161
+ .thumbnails [class*="span"] {
162
+ width: auto;
163
+ }
164
+ input[class*="span"],
165
+ select[class*="span"],
166
+ textarea[class*="span"],
167
+ .uneditable-input {
168
+ display: block;
169
+ width: 100%;
170
+ min-height: 28px;
171
+ /* Make inputs at least the height of their button counterpart */
172
+
173
+ /* Makes inputs behave like true block-level elements */
174
+
175
+ -webkit-box-sizing: border-box;
176
+ -moz-box-sizing: border-box;
177
+ -ms-box-sizing: border-box;
178
+ box-sizing: border-box;
179
+ }
180
+ .input-prepend input[class*="span"],
181
+ .input-append input[class*="span"] {
182
+ width: auto;
183
+ }
184
+ }
185
+ @media (min-width: 768px) and (max-width: 979px) {
186
+ .row {
187
+ margin-left: -20px;
188
+ *zoom: 1;
189
+ }
190
+ .row:before,
191
+ .row:after {
192
+ display: table;
193
+ content: "";
194
+ }
195
+ .row:after {
196
+ clear: both;
197
+ }
198
+ [class*="span"] {
199
+ float: left;
200
+ margin-left: 20px;
201
+ }
202
+ .container,
203
+ .navbar-fixed-top .container,
204
+ .navbar-fixed-bottom .container {
205
+ width: 724px;
206
+ }
207
+ .span12 {
208
+ width: 724px;
209
+ }
210
+ .span11 {
211
+ width: 662px;
212
+ }
213
+ .span10 {
214
+ width: 600px;
215
+ }
216
+ .span9 {
217
+ width: 538px;
218
+ }
219
+ .span8 {
220
+ width: 476px;
221
+ }
222
+ .span7 {
223
+ width: 414px;
224
+ }
225
+ .span6 {
226
+ width: 352px;
227
+ }
228
+ .span5 {
229
+ width: 290px;
230
+ }
231
+ .span4 {
232
+ width: 228px;
233
+ }
234
+ .span3 {
235
+ width: 166px;
236
+ }
237
+ .span2 {
238
+ width: 104px;
239
+ }
240
+ .span1 {
241
+ width: 42px;
242
+ }
243
+ .offset12 {
244
+ margin-left: 764px;
245
+ }
246
+ .offset11 {
247
+ margin-left: 702px;
248
+ }
249
+ .offset10 {
250
+ margin-left: 640px;
251
+ }
252
+ .offset9 {
253
+ margin-left: 578px;
254
+ }
255
+ .offset8 {
256
+ margin-left: 516px;
257
+ }
258
+ .offset7 {
259
+ margin-left: 454px;
260
+ }
261
+ .offset6 {
262
+ margin-left: 392px;
263
+ }
264
+ .offset5 {
265
+ margin-left: 330px;
266
+ }
267
+ .offset4 {
268
+ margin-left: 268px;
269
+ }
270
+ .offset3 {
271
+ margin-left: 206px;
272
+ }
273
+ .offset2 {
274
+ margin-left: 144px;
275
+ }
276
+ .offset1 {
277
+ margin-left: 82px;
278
+ }
279
+ .row-fluid {
280
+ width: 100%;
281
+ *zoom: 1;
282
+ }
283
+ .row-fluid:before,
284
+ .row-fluid:after {
285
+ display: table;
286
+ content: "";
287
+ }
288
+ .row-fluid:after {
289
+ clear: both;
290
+ }
291
+ .row-fluid > [class*="span"] {
292
+ float: left;
293
+ margin-left: 2.762430939%;
294
+ }
295
+ .row-fluid > [class*="span"]:first-child {
296
+ margin-left: 0;
297
+ }
298
+ .row-fluid > .span12 {
299
+ width: 99.999999993%;
300
+ }
301
+ .row-fluid > .span11 {
302
+ width: 91.436464082%;
303
+ }
304
+ .row-fluid > .span10 {
305
+ width: 82.87292817100001%;
306
+ }
307
+ .row-fluid > .span9 {
308
+ width: 74.30939226%;
309
+ }
310
+ .row-fluid > .span8 {
311
+ width: 65.74585634900001%;
312
+ }
313
+ .row-fluid > .span7 {
314
+ width: 57.182320438000005%;
315
+ }
316
+ .row-fluid > .span6 {
317
+ width: 48.618784527%;
318
+ }
319
+ .row-fluid > .span5 {
320
+ width: 40.055248616%;
321
+ }
322
+ .row-fluid > .span4 {
323
+ width: 31.491712705%;
324
+ }
325
+ .row-fluid > .span3 {
326
+ width: 22.928176794%;
327
+ }
328
+ .row-fluid > .span2 {
329
+ width: 14.364640883%;
330
+ }
331
+ .row-fluid > .span1 {
332
+ width: 5.801104972%;
333
+ }
334
+ input,
335
+ textarea,
336
+ .uneditable-input {
337
+ margin-left: 0;
338
+ }
339
+ input.span12, textarea.span12, .uneditable-input.span12 {
340
+ width: 714px;
341
+ }
342
+ input.span11, textarea.span11, .uneditable-input.span11 {
343
+ width: 652px;
344
+ }
345
+ input.span10, textarea.span10, .uneditable-input.span10 {
346
+ width: 590px;
347
+ }
348
+ input.span9, textarea.span9, .uneditable-input.span9 {
349
+ width: 528px;
350
+ }
351
+ input.span8, textarea.span8, .uneditable-input.span8 {
352
+ width: 466px;
353
+ }
354
+ input.span7, textarea.span7, .uneditable-input.span7 {
355
+ width: 404px;
356
+ }
357
+ input.span6, textarea.span6, .uneditable-input.span6 {
358
+ width: 342px;
359
+ }
360
+ input.span5, textarea.span5, .uneditable-input.span5 {
361
+ width: 280px;
362
+ }
363
+ input.span4, textarea.span4, .uneditable-input.span4 {
364
+ width: 218px;
365
+ }
366
+ input.span3, textarea.span3, .uneditable-input.span3 {
367
+ width: 156px;
368
+ }
369
+ input.span2, textarea.span2, .uneditable-input.span2 {
370
+ width: 94px;
371
+ }
372
+ input.span1, textarea.span1, .uneditable-input.span1 {
373
+ width: 32px;
374
+ }
375
+ }
376
+ @media (max-width: 979px) {
377
+ body {
378
+ padding-top: 0;
379
+ }
380
+ .navbar-fixed-top {
381
+ position: static;
382
+ margin-bottom: 18px;
383
+ }
384
+ .navbar-fixed-top .navbar-inner {
385
+ padding: 5px;
386
+ }
387
+ .navbar .container {
388
+ width: auto;
389
+ padding: 0;
390
+ }
391
+ .navbar .brand {
392
+ padding-left: 10px;
393
+ padding-right: 10px;
394
+ margin: 0 0 0 -5px;
395
+ }
396
+ .navbar .nav-collapse {
397
+ clear: left;
398
+ }
399
+ .navbar .nav {
400
+ float: none;
401
+ margin: 0 0 9px;
402
+ }
403
+ .navbar .nav > li {
404
+ float: none;
405
+ }
406
+ .navbar .nav > li > a {
407
+ margin-bottom: 2px;
408
+ }
409
+ .navbar .nav > .divider-vertical {
410
+ display: none;
411
+ }
412
+ .navbar .nav .nav-header {
413
+ color: #999999;
414
+ text-shadow: none;
415
+ }
416
+ .navbar .nav > li > a,
417
+ .navbar .dropdown-menu a {
418
+ padding: 6px 15px;
419
+ font-weight: bold;
420
+ color: #999999;
421
+ -webkit-border-radius: 3px;
422
+ -moz-border-radius: 3px;
423
+ border-radius: 3px;
424
+ }
425
+ .navbar .dropdown-menu li + li a {
426
+ margin-bottom: 2px;
427
+ }
428
+ .navbar .nav > li > a:hover,
429
+ .navbar .dropdown-menu a:hover {
430
+ background-color: #222222;
431
+ }
432
+ .navbar .dropdown-menu {
433
+ position: static;
434
+ top: auto;
435
+ left: auto;
436
+ float: none;
437
+ display: block;
438
+ max-width: none;
439
+ margin: 0 15px;
440
+ padding: 0;
441
+ background-color: transparent;
442
+ border: none;
443
+ -webkit-border-radius: 0;
444
+ -moz-border-radius: 0;
445
+ border-radius: 0;
446
+ -webkit-box-shadow: none;
447
+ -moz-box-shadow: none;
448
+ box-shadow: none;
449
+ }
450
+ .navbar .dropdown-menu:before,
451
+ .navbar .dropdown-menu:after {
452
+ display: none;
453
+ }
454
+ .navbar .dropdown-menu .divider {
455
+ display: none;
456
+ }
457
+ .navbar-form,
458
+ .navbar-search {
459
+ float: none;
460
+ padding: 9px 15px;
461
+ margin: 9px 0;
462
+ border-top: 1px solid #222222;
463
+ border-bottom: 1px solid #222222;
464
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
465
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
466
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
467
+ }
468
+ .navbar .nav.pull-right {
469
+ float: none;
470
+ margin-left: 0;
471
+ }
472
+ .navbar-static .navbar-inner {
473
+ padding-left: 10px;
474
+ padding-right: 10px;
475
+ }
476
+ .btn-navbar {
477
+ display: block;
478
+ }
479
+ .nav-collapse {
480
+ overflow: hidden;
481
+ height: 0;
482
+ }
483
+ }
484
+ @media (min-width: 980px) {
485
+ .nav-collapse.collapse {
486
+ height: auto !important;
487
+ overflow: visible !important;
488
+ }
489
+ }
490
+ @media (min-width: 1200px) {
491
+ .row {
492
+ margin-left: -30px;
493
+ *zoom: 1;
494
+ }
495
+ .row:before,
496
+ .row:after {
497
+ display: table;
498
+ content: "";
499
+ }
500
+ .row:after {
501
+ clear: both;
502
+ }
503
+ [class*="span"] {
504
+ float: left;
505
+ margin-left: 30px;
506
+ }
507
+ .container,
508
+ .navbar-fixed-top .container,
509
+ .navbar-fixed-bottom .container {
510
+ width: 1170px;
511
+ }
512
+ .span12 {
513
+ width: 1170px;
514
+ }
515
+ .span11 {
516
+ width: 1070px;
517
+ }
518
+ .span10 {
519
+ width: 970px;
520
+ }
521
+ .span9 {
522
+ width: 870px;
523
+ }
524
+ .span8 {
525
+ width: 770px;
526
+ }
527
+ .span7 {
528
+ width: 670px;
529
+ }
530
+ .span6 {
531
+ width: 570px;
532
+ }
533
+ .span5 {
534
+ width: 470px;
535
+ }
536
+ .span4 {
537
+ width: 370px;
538
+ }
539
+ .span3 {
540
+ width: 270px;
541
+ }
542
+ .span2 {
543
+ width: 170px;
544
+ }
545
+ .span1 {
546
+ width: 70px;
547
+ }
548
+ .offset12 {
549
+ margin-left: 1230px;
550
+ }
551
+ .offset11 {
552
+ margin-left: 1130px;
553
+ }
554
+ .offset10 {
555
+ margin-left: 1030px;
556
+ }
557
+ .offset9 {
558
+ margin-left: 930px;
559
+ }
560
+ .offset8 {
561
+ margin-left: 830px;
562
+ }
563
+ .offset7 {
564
+ margin-left: 730px;
565
+ }
566
+ .offset6 {
567
+ margin-left: 630px;
568
+ }
569
+ .offset5 {
570
+ margin-left: 530px;
571
+ }
572
+ .offset4 {
573
+ margin-left: 430px;
574
+ }
575
+ .offset3 {
576
+ margin-left: 330px;
577
+ }
578
+ .offset2 {
579
+ margin-left: 230px;
580
+ }
581
+ .offset1 {
582
+ margin-left: 130px;
583
+ }
584
+ .row-fluid {
585
+ width: 100%;
586
+ *zoom: 1;
587
+ }
588
+ .row-fluid:before,
589
+ .row-fluid:after {
590
+ display: table;
591
+ content: "";
592
+ }
593
+ .row-fluid:after {
594
+ clear: both;
595
+ }
596
+ .row-fluid > [class*="span"] {
597
+ float: left;
598
+ margin-left: 2.564102564%;
599
+ }
600
+ .row-fluid > [class*="span"]:first-child {
601
+ margin-left: 0;
602
+ }
603
+ .row-fluid > .span12 {
604
+ width: 100%;
605
+ }
606
+ .row-fluid > .span11 {
607
+ width: 91.45299145300001%;
608
+ }
609
+ .row-fluid > .span10 {
610
+ width: 82.905982906%;
611
+ }
612
+ .row-fluid > .span9 {
613
+ width: 74.358974359%;
614
+ }
615
+ .row-fluid > .span8 {
616
+ width: 65.81196581200001%;
617
+ }
618
+ .row-fluid > .span7 {
619
+ width: 57.264957265%;
620
+ }
621
+ .row-fluid > .span6 {
622
+ width: 48.717948718%;
623
+ }
624
+ .row-fluid > .span5 {
625
+ width: 40.170940171000005%;
626
+ }
627
+ .row-fluid > .span4 {
628
+ width: 31.623931624%;
629
+ }
630
+ .row-fluid > .span3 {
631
+ width: 23.076923077%;
632
+ }
633
+ .row-fluid > .span2 {
634
+ width: 14.529914530000001%;
635
+ }
636
+ .row-fluid > .span1 {
637
+ width: 5.982905983%;
638
+ }
639
+ input,
640
+ textarea,
641
+ .uneditable-input {
642
+ margin-left: 0;
643
+ }
644
+ input.span12, textarea.span12, .uneditable-input.span12 {
645
+ width: 1160px;
646
+ }
647
+ input.span11, textarea.span11, .uneditable-input.span11 {
648
+ width: 1060px;
649
+ }
650
+ input.span10, textarea.span10, .uneditable-input.span10 {
651
+ width: 960px;
652
+ }
653
+ input.span9, textarea.span9, .uneditable-input.span9 {
654
+ width: 860px;
655
+ }
656
+ input.span8, textarea.span8, .uneditable-input.span8 {
657
+ width: 760px;
658
+ }
659
+ input.span7, textarea.span7, .uneditable-input.span7 {
660
+ width: 660px;
661
+ }
662
+ input.span6, textarea.span6, .uneditable-input.span6 {
663
+ width: 560px;
664
+ }
665
+ input.span5, textarea.span5, .uneditable-input.span5 {
666
+ width: 460px;
667
+ }
668
+ input.span4, textarea.span4, .uneditable-input.span4 {
669
+ width: 360px;
670
+ }
671
+ input.span3, textarea.span3, .uneditable-input.span3 {
672
+ width: 260px;
673
+ }
674
+ input.span2, textarea.span2, .uneditable-input.span2 {
675
+ width: 160px;
676
+ }
677
+ input.span1, textarea.span1, .uneditable-input.span1 {
678
+ width: 60px;
679
+ }
680
+ .thumbnails {
681
+ margin-left: -30px;
682
+ }
683
+ .thumbnails > li {
684
+ margin-left: 30px;
685
+ }
686
+ }