quartz_flow 0.0.1 → 0.0.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 (35) hide show
  1. data/etc/quartz.rb +1 -1
  2. data/lib/quartz_flow/home.rb +33 -0
  3. data/lib/quartz_flow/plugin.rb +69 -0
  4. data/lib/quartz_flow/server.rb +33 -4
  5. data/lib/quartz_flow/settings_helper.rb +22 -1
  6. data/lib/quartz_flow/torrent_manager.rb +53 -6
  7. data/lib/quartz_flow/wrappers.rb +1 -0
  8. data/public/bootstrap/css/bootstrap-theme.css +459 -0
  9. data/public/bootstrap/css/bootstrap-theme.min.css +9 -0
  10. data/public/bootstrap/css/bootstrap.css +4927 -3996
  11. data/public/bootstrap/css/bootstrap.min.css +6 -6
  12. data/public/bootstrap/js/bootstrap.js +1156 -1434
  13. data/public/bootstrap/js/bootstrap.min.js +8 -5
  14. data/public/{bootstrap → bootstrap-2.3.2}/css/bootstrap-responsive.css +0 -0
  15. data/public/{bootstrap → bootstrap-2.3.2}/css/bootstrap-responsive.min.css +0 -0
  16. data/public/bootstrap-2.3.2/css/bootstrap.css +6167 -0
  17. data/public/bootstrap-2.3.2/css/bootstrap.min.css +9 -0
  18. data/public/{bootstrap → bootstrap-2.3.2}/img/glyphicons-halflings-white.png +0 -0
  19. data/public/{bootstrap → bootstrap-2.3.2}/img/glyphicons-halflings.png +0 -0
  20. data/public/bootstrap-2.3.2/js/bootstrap.js +2280 -0
  21. data/public/bootstrap-2.3.2/js/bootstrap.min.js +6 -0
  22. data/public/bootstrap-3.0.2/css/bootstrap-theme.css +459 -0
  23. data/public/bootstrap-3.0.2/css/bootstrap-theme.min.css +9 -0
  24. data/public/bootstrap-3.0.2/css/bootstrap.css +7098 -0
  25. data/public/bootstrap-3.0.2/css/bootstrap.min.css +9 -0
  26. data/public/bootstrap-3.0.2/js/bootstrap.js +2002 -0
  27. data/public/bootstrap-3.0.2/js/bootstrap.min.js +9 -0
  28. data/public/js/quartz.js +118 -22
  29. data/public/style.css +24 -0
  30. data/views/config_partial.haml +18 -18
  31. data/views/index.haml +17 -1
  32. data/views/menu_partial.haml +9 -0
  33. data/views/torrent_detail_partial.haml +21 -17
  34. data/views/torrent_table_partial.haml +29 -27
  35. metadata +24 -10
data/etc/quartz.rb CHANGED
@@ -7,7 +7,7 @@ set :bind, "0.0.0.0"
7
7
  set :port, 4444
8
8
 
9
9
  # Directory where downloaded torrent data will be stored
10
- set :basedir, "download"
10
+ set :basedir, "/mnt/twotb/movies"
11
11
 
12
12
  # Directory where .torrent files and .info files will be stored.
13
13
  set :metadir, "meta"
@@ -14,6 +14,7 @@ class Home
14
14
  "public",
15
15
  "db",
16
16
  "views",
17
+ "plugins",
17
18
  ]
18
19
 
19
20
  @installRoot = Home.determineAppRoot("quartz_flow")
@@ -62,6 +63,8 @@ class Home
62
63
  DataMapper.auto_upgrade!
63
64
  end
64
65
 
66
+ # Install plugins.
67
+ setupPlugins
65
68
  end
66
69
 
67
70
  def self.determineAppRoot(gemname)
@@ -73,4 +76,34 @@ class Home
73
76
  end
74
77
  end
75
78
 
79
+ private
80
+
81
+ def setupPlugins
82
+ # Find out the latest version of the quartz_flow gem
83
+ spec = Gem::Specification.find_by_name("quartz_flow")
84
+ if ! spec
85
+ puts "Not copying plugins: quartz_flow gem is not installed"
86
+ return
87
+ end
88
+
89
+ # If quartz_flow is pre-release, allow loading pre-release plugins.
90
+ allowPrerelease = spec.version.prerelease?
91
+
92
+ Gem::Specification.latest_specs(allowPrerelease).each do |spec|
93
+ if spec.name =~ /quartz_flow_plugin/
94
+ puts "Detected installed plugins gem '#{spec.name}'"
95
+ pluginBase = spec.full_gem_path
96
+ pluginContentsDir = pluginBase + File::SEPARATOR + "plugins"
97
+ Dir.new(pluginContentsDir).each do |e|
98
+ next if e[0,1] == '.'
99
+ path = pluginContentsDir + File::SEPARATOR + e
100
+ if File.directory?(path)
101
+ puts "Copying plugin #{e}"
102
+ FileUtils.cp_r path, @dir + File::SEPARATOR + "plugins"
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
76
109
  end
@@ -0,0 +1,69 @@
1
+ # Context used when executing the plugin's links.rb script.
2
+ # This class defines the 'functions' that the links.rb script can call.
3
+ class PluginContext
4
+ def initialize
5
+ @links = []
6
+ end
7
+
8
+ attr_reader :links
9
+
10
+ def link(name, path)
11
+ @links.push [name, path]
12
+ end
13
+ end
14
+
15
+ # This class is used to load QuartzFlow plugins (loadAll), and represents a single plugin.
16
+ # Plugins contain:
17
+ # - a links.rb file that defines menu links that show up on the main QuartzFlow page
18
+ # - a routes.rb file that defines new Sinatra routes
19
+ # - a views directory that contains new templates for use with the routes defined in routes.rb
20
+ class Plugin
21
+
22
+ def initialize(links, routesFile)
23
+ @links = links
24
+ @routesFile = routesFile
25
+ end
26
+
27
+ attr_reader :links
28
+ attr_reader :routesFile
29
+
30
+ # Load all plugins under plugins/ and return an array of the loaded Plugin objects.
31
+ def self.loadAll
32
+ plugins = []
33
+ Dir.new("plugins").each do |e|
34
+ next if e =~ /^\./
35
+ path = "plugins" + File::SEPARATOR + e
36
+ if File.directory?(path)
37
+
38
+ puts "Loading plugin #{e}"
39
+
40
+ links = loadPluginLinks(path)
41
+ routesFile = path + File::SEPARATOR + "routes.rb"
42
+ if ! File.exists?(routesFile)
43
+ routesFile = nil
44
+ puts " plugin has no routes.rb file"
45
+ end
46
+
47
+ plugins.push Plugin.new(links, routesFile)
48
+ end
49
+ end
50
+ plugins
51
+ end
52
+
53
+ private
54
+ # Load the links.rb file.
55
+ def self.loadPluginLinks(pluginDirectory)
56
+ links = []
57
+ path = pluginDirectory + File::SEPARATOR + "links.rb"
58
+ if File.exists?(path)
59
+ File.open(path,"r") do |file|
60
+ context = PluginContext.new
61
+ context.instance_eval file.read, "links.rb"
62
+ links = context.links
63
+ end
64
+ else
65
+ puts " plugin has no links.rb file"
66
+ end
67
+ links
68
+ end
69
+ end
@@ -14,6 +14,7 @@ require 'quartz_flow/torrent_manager'
14
14
  require 'quartz_flow/settings_helper'
15
15
  require 'quartz_flow/authentication'
16
16
  require 'quartz_flow/session'
17
+ require 'quartz_flow/plugin'
17
18
  require 'fileutils'
18
19
  require 'sinatra/base'
19
20
 
@@ -30,6 +31,9 @@ class LogConfigurator
30
31
  end
31
32
  end
32
33
 
34
+ # Load plugins
35
+ $plugins = Plugin.loadAll
36
+
33
37
  class Server < Sinatra::Base
34
38
  configure do
35
39
  enable :sessions
@@ -48,6 +52,16 @@ class Server < Sinatra::Base
48
52
 
49
53
  set :root, '.'
50
54
 
55
+ menuLinks = []
56
+ menuLinks.push ["Main", "/#"]
57
+ menuLinks.push ["Config", "/#/config"]
58
+ $plugins.each do |p|
59
+ p.links.each do |l|
60
+ menuLinks.push l
61
+ end
62
+ end
63
+ set :menuLinks, menuLinks
64
+
51
65
  raise "The basedir '#{settings.basedir}' does not exist. Please create it." if ! File.directory? settings.basedir
52
66
  raise "The metadir '#{settings.metadir}' does not exist. Please create it." if ! File.directory? settings.metadir
53
67
 
@@ -63,6 +77,7 @@ class Server < Sinatra::Base
63
77
  peerClient.start
64
78
 
65
79
  # Initialize Datamapper
80
+ #DataMapper::Logger.new($stdout, :debug)
66
81
  path = "sqlite://#{Dir.pwd}/#{settings.db_file}"
67
82
  DataMapper.setup(:default, path)
68
83
 
@@ -117,26 +132,33 @@ class Server < Sinatra::Base
117
132
  # Get the HTML template used by the Angular module
118
133
  # to display the table of running torrents view.
119
134
  get "/torrent_table" do
120
- haml :torrent_table_partial
135
+ menu = haml :menu_partial, :locals => { :links => settings.menuLinks, :active_link => "Main" }
136
+ haml :torrent_table_partial, :locals => { :menu => menu }
121
137
  end
122
138
 
123
139
  # Get the HTML template used by the Angular module
124
140
  # to display the details of a single running torrent view.
125
141
  get "/torrent_detail" do
126
- haml :torrent_detail_partial
142
+ menu = haml :menu_partial, :locals => { :links => settings.menuLinks }
143
+ haml :torrent_detail_partial, :locals => { :menu => menu }
127
144
  end
128
145
 
129
146
 
130
147
  # Get the HTML template used by the Angular module
131
148
  # to display the config settings
132
149
  get "/config" do
133
- haml :config_partial
150
+ menu = haml :menu_partial, :locals => { :links => settings.menuLinks, :active_link => "Config" }
151
+ haml :config_partial, :locals => { :menu => menu }
134
152
  end
135
153
 
136
154
  # Get an array of JSON objects that represent a list of current running
137
155
  # torrents with various properties.
138
156
  get "/torrent_data" do
139
- JSON.generate $manager.simplifiedTorrentData
157
+ fields = nil
158
+ fields = JSON.parse(params[:fields]).collect{ |f| f.to_sym } if params[:fields]
159
+ where = nil
160
+ where = JSON.parse(params[:where]) if params[:where]
161
+ JSON.generate $manager.simplifiedTorrentData(fields, where)
140
162
  end
141
163
 
142
164
  # Get usage as a JSON object.
@@ -207,6 +229,7 @@ class Server < Sinatra::Base
207
229
  # Handle an upload of a torrent file.
208
230
  post "/upload_torrent" do
209
231
  # See http://www.wooptoot.com/file-upload-with-sinatra
232
+ halt 500, "Starting torrent file failed: no torrent was given" if ! params['torrentfile']
210
233
  path = params['torrentfile'][:tempfile].path
211
234
 
212
235
  #FileUtils.chmod 0644, path
@@ -302,4 +325,10 @@ class Server < Sinatra::Base
302
325
  "OK"
303
326
  end
304
327
 
328
+ # Evaluate plugin routes
329
+
330
+ $plugins.each do |plugin|
331
+ eval File.open(plugin.routesFile,"r").read
332
+ end
333
+
305
334
  end
@@ -44,6 +44,14 @@ class SettingsHelper
44
44
  QuartzTorrent::Formatter.parseSize(v)
45
45
  end
46
46
  end
47
+
48
+ @@saveFilterForDuration = Proc.new do |v|
49
+ if v.nil? || v.length == 0
50
+ nil
51
+ else
52
+ QuartzTorrent::Formatter.parseTime(v)
53
+ end
54
+ end
47
55
 
48
56
 
49
57
  @@settingsMetainfo = {
@@ -65,6 +73,12 @@ class SettingsHelper
65
73
  @@floatValidator,
66
74
  Proc.new{ |v| v.to_f }
67
75
  ),
76
+ :defaultUploadDuration => SettingMetainfo.new(
77
+ :defaultUploadDuration,
78
+ :global,
79
+ @@saveFilterForDuration,
80
+ Proc.new{ |v| QuartzTorrent::Formatter.formatTime(v) }
81
+ ),
68
82
  :uploadRateLimit => SettingMetainfo.new(
69
83
  :uploadRateLimit,
70
84
  :torrent,
@@ -83,6 +97,12 @@ class SettingsHelper
83
97
  @@floatValidator,
84
98
  Proc.new{ |v| v.to_f }
85
99
  ),
100
+ :uploadDuration => SettingMetainfo.new(
101
+ :uploadDuration,
102
+ :torrent,
103
+ @@saveFilterForDuration,
104
+ Proc.new{ |v| QuartzTorrent::Formatter.formatTime(v) }
105
+ ),
86
106
  }
87
107
 
88
108
  def set(settingName, value, owner = nil)
@@ -98,7 +118,8 @@ class SettingsHelper
98
118
  settingModel = loadWithOwner(settingName, owner)
99
119
 
100
120
  if ! settingModel
101
- Setting.create( :name => settingName, :value => value, :scope => metaInfo.scope, :owner => owner )
121
+ settingModel = Setting.create( :name => settingName, :value => value, :scope => metaInfo.scope, :owner => owner )
122
+ settingModel.save
102
123
  else
103
124
  settingModel.value = value
104
125
  settingModel.save
@@ -39,21 +39,42 @@ class TorrentManager
39
39
  path = @torrentFileDir + File::SEPARATOR + e
40
40
  if e =~ /\.torrent$/
41
41
  puts "Starting .torrent '#{path}'"
42
- startTorrentFile(path)
42
+ begin
43
+ startTorrentFile(path)
44
+ rescue
45
+ puts " Starting .torrent '#{path}' failed: #{$!}"
46
+ end
43
47
  elsif e =~ /\.magnet$/
44
48
  magnet = loadMagnet(path)
45
49
  puts "Starting magnet '#{magnet.raw}'"
46
- startMagnet magnet
50
+ begin
51
+ startMagnet magnet
52
+ rescue
53
+ puts " Starting magnet '#{magnet.raw}' failed: #{$!}"
54
+ end
47
55
  end
48
56
  end
49
57
  end
50
58
 
51
- # Convert torrent data such that:
52
- # - The TorrentDataDelegate objects are converted to hashes.
53
- def simplifiedTorrentData
59
+ # Return the torrent data as a hash. If `fields` is non-null, then only those fields are returned.
60
+ # If `where` is non-null, it should be a hash of fields for which the values must match to be returned.
61
+ def simplifiedTorrentData(fields, where)
54
62
  result = {}
63
+
64
+ fieldsHash = {}
65
+ fields.each{ |e| fieldsHash[e] = true } if fields
66
+
67
+ if where
68
+ w = {}
69
+ where.each do |k,v|
70
+ w[k.to_sym] = v
71
+ end
72
+ where = w
73
+ end
74
+
55
75
  torrentData.each do |k,d|
56
76
  h = d.to_h
77
+
57
78
  asciiInfoHash = QuartzTorrent::bytesToHex(h[:infoHash])
58
79
  h[:infoHash] = asciiInfoHash
59
80
  h[:downloadRate] = QuartzTorrent::Formatter.formatSpeed(h[:downloadRate])
@@ -81,11 +102,32 @@ class TorrentManager
81
102
  h[:uploadRateLimit] = QuartzTorrent::Formatter.formatSpeed(h[:uploadRateLimit])
82
103
  h[:downloadRateLimit] = QuartzTorrent::Formatter.formatSize(h[:downloadRateLimit])
83
104
  h[:bytesUploaded] = QuartzTorrent::Formatter.formatSize(h[:bytesUploaded])
84
- h[:bytesDownloaded] = QuartzTorrent::Formatter.formatSize(h[:bytesDownloaded])
105
+ h[:uploadDuration] = QuartzTorrent::Formatter.formatTime(h[:uploadDuration]) if h[:uploadDuration]
85
106
 
86
107
  h[:completePieces] = d.completePieceBitfield ? d.completePieceBitfield.countSet : 0
87
108
  h[:totalPieces] = d.completePieceBitfield ? d.completePieceBitfield.length : 0
88
109
 
110
+ if where
111
+ matches = true
112
+ where.each do |k,v|
113
+ if h[k] != v
114
+ matches = false
115
+ break
116
+ end
117
+ end
118
+ next if ! matches
119
+ end
120
+
121
+ if fields
122
+ newHash = {}
123
+ h.each do |k,v|
124
+ if fieldsHash.has_key?(k)
125
+ newHash[k] = v
126
+ end
127
+ end
128
+ h = newHash
129
+ end
130
+
89
131
  result[asciiInfoHash] = h
90
132
  end
91
133
  result
@@ -195,9 +237,14 @@ class TorrentManager
195
237
  ratio = helper.get(:ratio, :filter, asciiInfoHash)
196
238
  ratio = helper.get(:defaultRatio, :filter) if ! ratio
197
239
 
240
+ uploadDuration = helper.get(:uploadDuration, :unfiltered, asciiInfoHash)
241
+ uploadDuration = helper.get(:defaultUploadDuration, :unfiltered) if ! uploadDuration
242
+ uploadDuration = uploadDuration.to_i if uploadDuration
243
+
198
244
  @peerClient.setUploadRateLimit infoHash, uploadRateLimit
199
245
  @peerClient.setDownloadRateLimit infoHash, downloadRateLimit
200
246
  @peerClient.setUploadRatio infoHash, ratio
247
+ @peerClient.setUploadDuration infoHash, uploadDuration
201
248
  end
202
249
 
203
250
  # Get the usage for the current period of the specified type.
@@ -90,6 +90,7 @@ module QuartzTorrent
90
90
  result[:uploadRateLimit] = @uploadRateLimit
91
91
  result[:downloadRateLimit] = @downloadRateLimit
92
92
  result[:ratio] = @ratio
93
+ result[:uploadDuration] = @uploadDuration
93
94
  result[:bytesUploaded] = @bytesUploaded
94
95
  result[:bytesDownloaded] = @bytesDownloaded
95
96
 
@@ -0,0 +1,459 @@
1
+ /*!
2
+ * Bootstrap v3.0.2 by @fat and @mdo
3
+ * Copyright 2013 Twitter, Inc.
4
+ * Licensed under http://www.apache.org/licenses/LICENSE-2.0
5
+ *
6
+ * Designed and built with all the love in the world by @mdo and @fat.
7
+ */
8
+
9
+ .btn-default,
10
+ .btn-primary,
11
+ .btn-success,
12
+ .btn-info,
13
+ .btn-warning,
14
+ .btn-danger {
15
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
16
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
17
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
18
+ }
19
+
20
+ .btn-default:active,
21
+ .btn-primary:active,
22
+ .btn-success:active,
23
+ .btn-info:active,
24
+ .btn-warning:active,
25
+ .btn-danger:active,
26
+ .btn-default.active,
27
+ .btn-primary.active,
28
+ .btn-success.active,
29
+ .btn-info.active,
30
+ .btn-warning.active,
31
+ .btn-danger.active {
32
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
33
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
34
+ }
35
+
36
+ .btn:active,
37
+ .btn.active {
38
+ background-image: none;
39
+ }
40
+
41
+ .btn-default {
42
+ text-shadow: 0 1px 0 #fff;
43
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e0e0e0));
44
+ background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
45
+ background-image: -moz-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
46
+ background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
47
+ background-repeat: repeat-x;
48
+ border-color: #dbdbdb;
49
+ border-color: #ccc;
50
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
51
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
52
+ }
53
+
54
+ .btn-default:hover,
55
+ .btn-default:focus {
56
+ background-color: #e0e0e0;
57
+ background-position: 0 -15px;
58
+ }
59
+
60
+ .btn-default:active,
61
+ .btn-default.active {
62
+ background-color: #e0e0e0;
63
+ border-color: #dbdbdb;
64
+ }
65
+
66
+ .btn-primary {
67
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#2d6ca2));
68
+ background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
69
+ background-image: -moz-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
70
+ background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
71
+ background-repeat: repeat-x;
72
+ border-color: #2b669a;
73
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
74
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
75
+ }
76
+
77
+ .btn-primary:hover,
78
+ .btn-primary:focus {
79
+ background-color: #2d6ca2;
80
+ background-position: 0 -15px;
81
+ }
82
+
83
+ .btn-primary:active,
84
+ .btn-primary.active {
85
+ background-color: #2d6ca2;
86
+ border-color: #2b669a;
87
+ }
88
+
89
+ .btn-success {
90
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#419641));
91
+ background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
92
+ background-image: -moz-linear-gradient(top, #5cb85c 0%, #419641 100%);
93
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
94
+ background-repeat: repeat-x;
95
+ border-color: #3e8f3e;
96
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
97
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
98
+ }
99
+
100
+ .btn-success:hover,
101
+ .btn-success:focus {
102
+ background-color: #419641;
103
+ background-position: 0 -15px;
104
+ }
105
+
106
+ .btn-success:active,
107
+ .btn-success.active {
108
+ background-color: #419641;
109
+ border-color: #3e8f3e;
110
+ }
111
+
112
+ .btn-warning {
113
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#eb9316));
114
+ background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
115
+ background-image: -moz-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
116
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
117
+ background-repeat: repeat-x;
118
+ border-color: #e38d13;
119
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
120
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
121
+ }
122
+
123
+ .btn-warning:hover,
124
+ .btn-warning:focus {
125
+ background-color: #eb9316;
126
+ background-position: 0 -15px;
127
+ }
128
+
129
+ .btn-warning:active,
130
+ .btn-warning.active {
131
+ background-color: #eb9316;
132
+ border-color: #e38d13;
133
+ }
134
+
135
+ .btn-danger {
136
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c12e2a));
137
+ background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
138
+ background-image: -moz-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
139
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
140
+ background-repeat: repeat-x;
141
+ border-color: #b92c28;
142
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
143
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
144
+ }
145
+
146
+ .btn-danger:hover,
147
+ .btn-danger:focus {
148
+ background-color: #c12e2a;
149
+ background-position: 0 -15px;
150
+ }
151
+
152
+ .btn-danger:active,
153
+ .btn-danger.active {
154
+ background-color: #c12e2a;
155
+ border-color: #b92c28;
156
+ }
157
+
158
+ .btn-info {
159
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#2aabd2));
160
+ background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
161
+ background-image: -moz-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
162
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
163
+ background-repeat: repeat-x;
164
+ border-color: #28a4c9;
165
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
166
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
167
+ }
168
+
169
+ .btn-info:hover,
170
+ .btn-info:focus {
171
+ background-color: #2aabd2;
172
+ background-position: 0 -15px;
173
+ }
174
+
175
+ .btn-info:active,
176
+ .btn-info.active {
177
+ background-color: #2aabd2;
178
+ border-color: #28a4c9;
179
+ }
180
+
181
+ .thumbnail,
182
+ .img-thumbnail {
183
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
184
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
185
+ }
186
+
187
+ .dropdown-menu > li > a:hover,
188
+ .dropdown-menu > li > a:focus {
189
+ background-color: #e8e8e8;
190
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
191
+ background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
192
+ background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
193
+ background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
194
+ background-repeat: repeat-x;
195
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
196
+ }
197
+
198
+ .dropdown-menu > .active > a,
199
+ .dropdown-menu > .active > a:hover,
200
+ .dropdown-menu > .active > a:focus {
201
+ background-color: #357ebd;
202
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
203
+ background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
204
+ background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
205
+ background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
206
+ background-repeat: repeat-x;
207
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
208
+ }
209
+
210
+ .navbar-default {
211
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8));
212
+ background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
213
+ background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
214
+ background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
215
+ background-repeat: repeat-x;
216
+ border-radius: 4px;
217
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
218
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
219
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
220
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
221
+ }
222
+
223
+ .navbar-default .navbar-nav > .active > a {
224
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f3f3f3));
225
+ background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
226
+ background-image: -moz-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
227
+ background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
228
+ background-repeat: repeat-x;
229
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
230
+ -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
231
+ box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
232
+ }
233
+
234
+ .navbar-brand,
235
+ .navbar-nav > li > a {
236
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
237
+ }
238
+
239
+ .navbar-inverse {
240
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222));
241
+ background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
242
+ background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%);
243
+ background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
244
+ background-repeat: repeat-x;
245
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
246
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
247
+ }
248
+
249
+ .navbar-inverse .navbar-nav > .active > a {
250
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#222222), to(#282828));
251
+ background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%);
252
+ background-image: -moz-linear-gradient(top, #222222 0%, #282828 100%);
253
+ background-image: linear-gradient(to bottom, #222222 0%, #282828 100%);
254
+ background-repeat: repeat-x;
255
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
256
+ -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
257
+ box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
258
+ }
259
+
260
+ .navbar-inverse .navbar-brand,
261
+ .navbar-inverse .navbar-nav > li > a {
262
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
263
+ }
264
+
265
+ .navbar-static-top,
266
+ .navbar-fixed-top,
267
+ .navbar-fixed-bottom {
268
+ border-radius: 0;
269
+ }
270
+
271
+ .alert {
272
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
273
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
274
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
275
+ }
276
+
277
+ .alert-success {
278
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc));
279
+ background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
280
+ background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
281
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
282
+ background-repeat: repeat-x;
283
+ border-color: #b2dba1;
284
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
285
+ }
286
+
287
+ .alert-info {
288
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0));
289
+ background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
290
+ background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
291
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
292
+ background-repeat: repeat-x;
293
+ border-color: #9acfea;
294
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
295
+ }
296
+
297
+ .alert-warning {
298
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0));
299
+ background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
300
+ background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
301
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
302
+ background-repeat: repeat-x;
303
+ border-color: #f5e79e;
304
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
305
+ }
306
+
307
+ .alert-danger {
308
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3));
309
+ background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
310
+ background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
311
+ background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
312
+ background-repeat: repeat-x;
313
+ border-color: #dca7a7;
314
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
315
+ }
316
+
317
+ .progress {
318
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5));
319
+ background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
320
+ background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
321
+ background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
322
+ background-repeat: repeat-x;
323
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
324
+ }
325
+
326
+ .progress-bar {
327
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
328
+ background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
329
+ background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
330
+ background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
331
+ background-repeat: repeat-x;
332
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
333
+ }
334
+
335
+ .progress-bar-success {
336
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
337
+ background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
338
+ background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
339
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
340
+ background-repeat: repeat-x;
341
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
342
+ }
343
+
344
+ .progress-bar-info {
345
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
346
+ background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
347
+ background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
348
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
349
+ background-repeat: repeat-x;
350
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
351
+ }
352
+
353
+ .progress-bar-warning {
354
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
355
+ background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
356
+ background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
357
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
358
+ background-repeat: repeat-x;
359
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
360
+ }
361
+
362
+ .progress-bar-danger {
363
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
364
+ background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
365
+ background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
366
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
367
+ background-repeat: repeat-x;
368
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
369
+ }
370
+
371
+ .list-group {
372
+ border-radius: 4px;
373
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
374
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
375
+ }
376
+
377
+ .list-group-item.active,
378
+ .list-group-item.active:hover,
379
+ .list-group-item.active:focus {
380
+ text-shadow: 0 -1px 0 #3071a9;
381
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3));
382
+ background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
383
+ background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%);
384
+ background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
385
+ background-repeat: repeat-x;
386
+ border-color: #3278b3;
387
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
388
+ }
389
+
390
+ .panel {
391
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
392
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
393
+ }
394
+
395
+ .panel-default > .panel-heading {
396
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
397
+ background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
398
+ background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
399
+ background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
400
+ background-repeat: repeat-x;
401
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
402
+ }
403
+
404
+ .panel-primary > .panel-heading {
405
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
406
+ background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
407
+ background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
408
+ background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
409
+ background-repeat: repeat-x;
410
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
411
+ }
412
+
413
+ .panel-success > .panel-heading {
414
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6));
415
+ background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
416
+ background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
417
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
418
+ background-repeat: repeat-x;
419
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
420
+ }
421
+
422
+ .panel-info > .panel-heading {
423
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3));
424
+ background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
425
+ background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
426
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
427
+ background-repeat: repeat-x;
428
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
429
+ }
430
+
431
+ .panel-warning > .panel-heading {
432
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc));
433
+ background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
434
+ background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
435
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
436
+ background-repeat: repeat-x;
437
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
438
+ }
439
+
440
+ .panel-danger > .panel-heading {
441
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc));
442
+ background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
443
+ background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
444
+ background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
445
+ background-repeat: repeat-x;
446
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
447
+ }
448
+
449
+ .well {
450
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5));
451
+ background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
452
+ background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
453
+ background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
454
+ background-repeat: repeat-x;
455
+ border-color: #dcdcdc;
456
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
457
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
458
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
459
+ }