quartz_flow 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ }