qless 0.9.1 → 0.9.2

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 (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
+ }