ts-admin 0.2.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a38d80d69b3c2bbae9e237d74acf92504366ce6a
4
+ data.tar.gz: e84f8bf4205e3ed0120aeeb2534f8501c53cccea
5
+ SHA512:
6
+ metadata.gz: 443bd6c4b09851e653eaf1fa7d097c39c03d08b792e22b5a7b765b8ebc71b4248f6221c2a3332d6ec66e024dc035d85c78027f67b711cdc39b42a65489f4c6b0
7
+ data.tar.gz: f6700bed37709a9d606bbcfd7be7720969f07ebbf0a93f4ac641f3f1a690b73632160fd56c2f3fe5f8124ee7e8821d520c043de0b5b42b2150eefaae580f75c9
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in TSAdmin.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ts-admin (0.2.0)
5
+ ramaze (>= 2.0.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ innate (2013.02.21)
11
+ rack (~> 1.5.2)
12
+ rack (1.5.2)
13
+ rake (10.0.4)
14
+ ramaze (2012.12.08)
15
+ innate (>= 2012.12)
16
+ rake
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ bundler (~> 1.3)
23
+ rake
24
+ ts-admin!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 SIC! Software GmbH
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Apache Traffic Server Frontend
2
+
3
+ This is a simple web frontend for the remap.config configuration file for the Apache Traffic Server.
4
+ It is used to manage reverse proxy mappings and 301 redirects. Please refer to [this blog post](http://blog.sic-software.com/2012/03/02/eine-administrationsoberflache-fur-den-apache-traffic-server/) for more information.
5
+
6
+ ## Installation
7
+
8
+ $ gem install ts-admin
9
+
10
+ ## Usage
11
+
12
+ Create a configuration file:
13
+
14
+ ```yaml
15
+ port: 7000
16
+
17
+ auth:
18
+ username: admin
19
+ password: d033e22ae348aeb5660fc2140aec35850c4da997 # SHA1 hash
20
+
21
+ traffic_server:
22
+ config_path: /etc/trafficserver
23
+ restart_cmd: /etc/init.d/trafficserver restart
24
+
25
+ info:
26
+ external_ip: 192.168.0.1
27
+ ```
28
+
29
+ Start the server:
30
+
31
+ $ ts-admin --config /path/to/your/config.yml
32
+
33
+ ## Contributing
34
+
35
+ 1. Fork it
36
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
37
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
38
+ 4. Push to the branch (`git push origin my-new-feature`)
39
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ Dir.glob(File.expand_path('../task/*.rake', __FILE__)) { |task| import task }
data/app.rb ADDED
@@ -0,0 +1,21 @@
1
+ # This file contains your application, it requires dependencies and necessary
2
+ # parts of the application.
3
+ require 'rubygems'
4
+ require 'yaml'
5
+ require 'ramaze'
6
+
7
+ # Load app configuration
8
+ config_path = __DIR__('app.yml')
9
+ config_path = OPTIONS[:config] if defined?(OPTIONS)
10
+ APP_CONFIG = {
11
+ 'auth' => {
12
+ 'username' => 'admin',
13
+ 'password' => '-'
14
+ }
15
+ }.merge(YAML.load_file(config_path))
16
+
17
+ # Make sure that Ramaze knows where you are
18
+ Ramaze.options.roots = [__DIR__]
19
+
20
+ require __DIR__('lib/ts-admin/traffic_server')
21
+ require __DIR__('controller/init')
data/app.yml.example ADDED
@@ -0,0 +1,12 @@
1
+ port: 7000
2
+
3
+ auth:
4
+ username: admin
5
+ password: d033e22ae348aeb5660fc2140aec35850c4da997 # SHA1 hash
6
+
7
+ traffic_server:
8
+ config_path: /etc/trafficserver
9
+ restart_cmd: /etc/init.d/trafficserver restart
10
+
11
+ info:
12
+ external_ip: 192.168.0.1
data/bin/ts-admin ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Parse options
4
+ require 'optparse'
5
+
6
+ OPTIONS = {}
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: tsadmin [options]"
9
+
10
+ opts.on("-c", "--config [path]", "Port") do |config|
11
+ OPTIONS[:config] = config
12
+ end
13
+ opts.on("-p", "--port [port]", "Port") do |port|
14
+ OPTIONS[:port] = port.to_i
15
+ end
16
+ end.parse!
17
+
18
+ # Start TSAdmin
19
+ require File.expand_path('../../app', __FILE__)
20
+
21
+ Ramaze.start(
22
+ :adapter => :webrick,
23
+ :port => OPTIONS[:port] || APP_CONFIG['port'] || 7000,
24
+ :file => __FILE__,
25
+ :root => Ramaze.options.roots
26
+ )
data/config.ru ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env rackup
2
+ #
3
+ # config.ru for ramaze apps
4
+ #
5
+ # Rackup is a useful tool for running Rack applications, which uses the
6
+ # Rack::Builder DSL to configure middleware and build up applications easily.
7
+ #
8
+ # Rackup automatically figures out the environment it is run in, and runs your
9
+ # application as FastCGI, CGI, or standalone with Mongrel or WEBrick -- all from
10
+ # the same configuration.
11
+ #
12
+ # Do not set the adapter.handler in here, it will be ignored.
13
+ # You can choose the adapter like `ramaze start -s mongrel` or set it in the
14
+ # 'start.rb' and use `ruby start.rb` instead.
15
+ require ::File.expand_path('../app', __FILE__)
16
+
17
+ Ramaze.start(:root => Ramaze.options.roots, :started => true)
18
+
19
+ run Ramaze
@@ -0,0 +1,55 @@
1
+ # Define a subclass of Ramaze::Controller holding your defaults for all controllers. Note
2
+ # that these changes can be overwritten in sub controllers by simply calling the method
3
+ # but with a different value.
4
+
5
+ class Controller < Ramaze::Controller
6
+ layout :default
7
+ helper :auth
8
+ helper :xhtml
9
+ helper :link
10
+ helper :flash
11
+ engine :etanni
12
+
13
+ def login
14
+ if logged_in?
15
+ call(r('/'))
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def auth_template
24
+ <<-TEMPLATE.strip!
25
+ <div class="grid_12 content login"><header><h2>Login</h2></header><div class="list-form">#{super}</div></div>
26
+ TEMPLATE
27
+ end
28
+
29
+ def auth_login(username, password)
30
+ return unless username and password
31
+ return if username.empty? or password.empty?
32
+
33
+ return unless APP_CONFIG['auth']['username'].downcase == username.downcase
34
+ return unless APP_CONFIG['auth']['password'] == Digest::SHA1.hexdigest(password)
35
+
36
+ session[:logged_in] = true
37
+ session[:username] = APP_CONFIG['auth']['username']
38
+ end
39
+
40
+ def login_required
41
+ super
42
+ @username = session[:username]
43
+ end
44
+
45
+ def traffic_server
46
+ @_traffic_server ||= ::TSAdmin::TrafficServer.new(APP_CONFIG['traffic_server'])
47
+ end
48
+
49
+ end
50
+
51
+ # Here you can require all your other controllers. Note that if you have multiple
52
+ # controllers you might want to do something like the following:
53
+ Dir.glob("#{__DIR__}/*.rb").each do |controller|
54
+ require(controller)
55
+ end
@@ -0,0 +1,10 @@
1
+ class MainController < Controller
2
+
3
+ def index
4
+ return unless login_required
5
+
6
+ external_ip_fallback = '<span class="none">none</span>'
7
+ @external_ip = (APP_CONFIG['info']['external_ip2'] || external_ip_fallback) rescue external_ip_fallback
8
+ end
9
+
10
+ end
@@ -0,0 +1,86 @@
1
+ class RemapController < Controller
2
+
3
+ def index
4
+ return unless login_required
5
+ set_env
6
+
7
+ @traffic_server = traffic_server
8
+ end
9
+
10
+ def new
11
+ return unless login_required
12
+ set_env
13
+
14
+ if request.post?
15
+ @from = request[:from]
16
+ @to = request[:to]
17
+ if traffic_server.add_remap(@type, @from, @to)
18
+ traffic_server.save
19
+ traffic_server.restart
20
+ flash[:info] = "Remap entry added"
21
+ call(r('/'))
22
+ else
23
+ flash[:error] = "Invalid Remap entry"
24
+ render_view :form
25
+ end
26
+ else
27
+ render_view :form
28
+ end
29
+ end
30
+
31
+ def edit
32
+ return unless login_required
33
+ set_env
34
+
35
+ @id = request[:id]
36
+
37
+ if request.post?
38
+ @from = request[:from]
39
+ @to = request[:to]
40
+ if traffic_server.edit_remap(@id, @from, @to)
41
+ traffic_server.save
42
+ traffic_server.restart
43
+ flash[:info] = "Remap entry updated"
44
+ call(r('/'))
45
+ else
46
+ flash[:error] = "Invalid Remap entry"
47
+ render_view :form
48
+ end
49
+ else
50
+ @entry = traffic_server.find_remap_by_id(@id)
51
+ @from = @entry[:from]
52
+ @to = @entry[:to]
53
+ @type = @entry[:type]
54
+ render_view :form
55
+ end
56
+ end
57
+
58
+ def delete
59
+ return unless login_required
60
+ set_env
61
+
62
+ traffic_server.delete_remap(request[:id])
63
+ traffic_server.save
64
+ traffic_server.restart
65
+
66
+ flash[:info] = "Remap entry removed"
67
+
68
+ call(r('/'))
69
+ end
70
+
71
+ private
72
+
73
+ def remap_url_highlight_scheme(url)
74
+ scheme, hostpath = url.downcase.split(/:\/\//, 2)
75
+ <<-LINK.strip!
76
+ <span class="#{scheme}">#{scheme}</span>://#{hostpath}
77
+ LINK
78
+ end
79
+
80
+ def set_env
81
+ @title = 'Remap'
82
+ @nav = :remap
83
+ @type = request[:type]
84
+ end
85
+
86
+ end
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>TSAdmin#{" :: #{@title}" if defined?(@title)}</title>
6
+ #{css('reset')}
7
+ #{css('grid')}
8
+ #{css('layout')}
9
+ #{css('text')}
10
+ #{css('font-awesome.min')}
11
+ </head>
12
+ <body>
13
+ <div class="container">
14
+ <div id="container" class="row">
15
+ <div id="top" class="grid_12">
16
+ <header class="grid_6">
17
+ <h1><a href="/">TSAdmin</a></h1>
18
+ </header>
19
+
20
+ <?r if @username ?>
21
+ <nav class="grid_6 last">
22
+ <ul class="clearfix">
23
+ <li>
24
+ <a href="/remap" class="#{'current' if @nav == :remap}">Remap</a>
25
+ </li>
26
+ <li>
27
+ <a href="/logout">Logout</a>
28
+ </li>
29
+ </ul>
30
+ </nav>
31
+ <?r end ?>
32
+ </div>
33
+
34
+ #{flashbox}
35
+
36
+ #{@content}
37
+ </div>
38
+
39
+ <footer id="footer">
40
+ <p>
41
+ &copy; 2012-2013 SIC! Software GmbH<br />
42
+ TSAdmin is free software and is licensed under the MIT license.
43
+ </p>
44
+ </footer>
45
+ </div>
46
+ </body>
47
+ </html>
@@ -0,0 +1,157 @@
1
+ require 'digest/sha1'
2
+ require 'uri'
3
+
4
+ module TSAdmin
5
+ class TrafficServer
6
+ MARKER_TEXT = "CONFIGURATION FROM TSADMIN"
7
+
8
+ def initialize(options={})
9
+ @options = {
10
+ 'config_path' => '/etc/trafficserver',
11
+ 'restart_cmd' => '/etc/init.d/trafficserver restart'
12
+ }.merge(options)
13
+ @config_path = @options['config_path']
14
+ end
15
+
16
+ def redirects
17
+ load unless defined?(@redirects)
18
+ @redirects.dup.freeze
19
+ end
20
+
21
+ def maps
22
+ load unless defined?(@maps)
23
+ @maps.dup.freeze
24
+ end
25
+
26
+ def find_remap_by_id(id)
27
+ (maps + redirects).select{|m| m[:id] == id}.first
28
+ end
29
+
30
+ def add_remap(type, from, to, options={})
31
+ load
32
+
33
+ return unless list = case type.to_sym
34
+ when :map
35
+ @maps
36
+ when :redirect
37
+ @redirects
38
+ else
39
+ nil
40
+ end
41
+
42
+ id = Digest::SHA1.hexdigest("#{from}_#{to}")
43
+
44
+ return false if find_remap_by_id(id)
45
+ return false unless validate_url(from) && validate_url(to)
46
+
47
+ list << {:id => id, :type => type.to_sym, :from => from, :to => to, :options => options}
48
+
49
+ true
50
+ end
51
+
52
+ def edit_remap(id, from, to, options=nil)
53
+ return false unless validate_url(from) && validate_url(to)
54
+ return false unless entry = find_remap_by_id(id)
55
+ entry[:from] = from
56
+ entry[:to] = to
57
+ entry[:options] = options unless options.nil?
58
+ entry[:id] = Digest::SHA1.hexdigest("#{from}_#{to}")
59
+ end
60
+
61
+ def delete_remap(id)
62
+ load
63
+
64
+ @maps.delete_if{|m| m[:id] == id}
65
+ @redirects.delete_if{|m| m[:id] == id}
66
+
67
+ true
68
+ end
69
+
70
+ def save
71
+ own_config = ''
72
+ own_config << "#{marker_begin}\n"
73
+ redirects.each do |redirect|
74
+ own_config << "redirect #{redirect[:from]} #{redirect[:to]} #{redirect[:options] if redirect[:options]}\n"
75
+ end
76
+ maps.each do |map|
77
+ own_config << "map #{map[:from]} #{map[:to]} #{map[:options] if map[:options]}\n"
78
+ end
79
+ own_config << "#{marker_end}\n"
80
+
81
+ file_content = ''
82
+ in_config_block = false
83
+ own_config_written = false
84
+ File.read(remap_path).each_line do |line|
85
+ if line.strip == marker_begin
86
+ in_config_block = true
87
+ next
88
+ elsif line.strip == marker_end
89
+ in_config_block = false
90
+ next
91
+ end
92
+
93
+ if in_config_block
94
+ file_content << own_config unless own_config_written
95
+ own_config_written = true
96
+ else
97
+ file_content << line
98
+ end
99
+ end
100
+ file_content << own_config unless own_config_written
101
+
102
+ remap_file = File.new(remap_path, 'w')
103
+ remap_file.write(file_content)
104
+ remap_file.close
105
+ end
106
+
107
+ def restart
108
+ `#{@options['restart_cmd']}`
109
+ end
110
+
111
+ private
112
+
113
+ def marker_begin
114
+ "# BEGIN #{MARKER_TEXT}"
115
+ end
116
+
117
+ def marker_end
118
+ "# END #{MARKER_TEXT}"
119
+ end
120
+
121
+ def remap_path
122
+ File.join(@config_path, 'remap.config')
123
+ end
124
+
125
+ def load
126
+ @redirects = []
127
+ @maps = []
128
+
129
+ in_config_block = false
130
+ File.read(remap_path).each_line do |line|
131
+ if line.strip == marker_begin
132
+ in_config_block = true
133
+ next
134
+ elsif line.strip == marker_end
135
+ in_config_block = false
136
+ next
137
+ end
138
+
139
+ next unless in_config_block
140
+
141
+ type, from, to, *options = line.split(/\s+/)
142
+ id = Digest::SHA1.hexdigest("#{from}_#{to}")
143
+ case type.to_sym
144
+ when :redirect
145
+ @redirects << {:id => id, :type => type.to_sym, :from => from, :to => to, :options => {}}
146
+ when :map
147
+ @maps << {:id => id, :type => type.to_sym, :from => from, :to => to, :options => {}}
148
+ end
149
+ end
150
+ end
151
+
152
+ def validate_url(url)
153
+ !!(url =~ URI::regexp) rescue false
154
+ end
155
+
156
+ end
157
+ end
@@ -0,0 +1,3 @@
1
+ module TSAdmin
2
+ VERSION = "0.2.0"
3
+ end
data/lib/ts-admin.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "ts-admin/version"
2
+ require "ts-admin/traffic_server"
3
+
4
+ module TSAdmin
5
+ end
@@ -0,0 +1,24 @@
1
+ /*!
2
+ * Font Awesome 3.1.0
3
+ * the iconic font designed for Bootstrap
4
+ * -------------------------------------------------------
5
+ * The full suite of pictographic icons, examples, and documentation
6
+ * can be found at: http://fontawesome.io
7
+ *
8
+ * License
9
+ * -------------------------------------------------------
10
+ * - The Font Awesome font is licensed under the SIL Open Font License v1.1 -
11
+ * http://scripts.sil.org/OFL
12
+ * - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License -
13
+ * http://opensource.org/licenses/mit-license.html
14
+ * - Font Awesome documentation licensed under CC BY 3.0 License -
15
+ * http://creativecommons.org/licenses/by/3.0/
16
+ * - Attribution is no longer required in Font Awesome 3.0, but much appreciated:
17
+ * "Font Awesome by Dave Gandy - http://fontawesome.io"
18
+
19
+ * Contact
20
+ * -------------------------------------------------------
21
+ * Email: dave@fontawesome.io
22
+ * Twitter: http://twitter.com/fortaweso_me
23
+ * Work: Lead Product Designer @ http://kyruus.com
24
+ */@font-face{font-family:'FontAwesome';src:url('../font/fontawesome-webfont.eot?v=3.1.0');src:url('../font/fontawesome-webfont.eot?#iefix&v=3.1.0') format('embedded-opentype'),url('../font/fontawesome-webfont.woff?v=3.1.0') format('woff'),url('../font/fontawesome-webfont.ttf?v=3.1.0') format('truetype'),url('../font/fontawesome-webfont.svg#fontawesomeregular?v=3.1.0') format('svg');font-weight:normal;font-style:normal}[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;*margin-right:.3em}[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}a [class^="icon-"],a [class*=" icon-"],a [class^="icon-"]:before,a [class*=" icon-"]:before{display:inline}[class^="icon-"].icon-fixed-width,[class*=" icon-"].icon-fixed-width{display:inline-block;width:1.2857142857142858em;text-align:center}[class^="icon-"].icon-fixed-width.icon-large,[class*=" icon-"].icon-fixed-width.icon-large{width:1.5714285714285714em}ul.icons-ul{list-style-type:none;text-indent:-0.7142857142857143em;margin-left:2.142857142857143em}ul.icons-ul>li .icon-li{width:.7142857142857143em;display:inline-block;text-align:center}[class^="icon-"].hide,[class*=" icon-"].hide{display:none}.icon-muted{color:#eee}.icon-light{color:#fff}.icon-dark{color:#333}.icon-border{border:solid 1px #eee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.icon-5x{font-size:5em}.icon-5x.icon-border{border-width:5px;-webkit-border-radius:7px;-moz-border-radius:7px;border-radius:7px}.pull-right{float:right}.pull-left{float:left}[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}[class^="icon-"],[class*=" icon-"]{display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}.icon-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:-35%}.icon-stack [class^="icon-"],.icon-stack [class*=" icon-"]{display:block;text-align:center;position:absolute;width:100%;height:100%;font-size:1em;line-height:inherit;*line-height:2em}.icon-stack .icon-stack-base{font-size:2em;*line-height:1em}.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.icon-rotate-90:before{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1)}.icon-rotate-180:before{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2)}.icon-rotate-270:before{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg);filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3)}.icon-flip-horizontal:before{-webkit-transform:scale(-1,1);-moz-transform:scale(-1,1);-ms-transform:scale(-1,1);-o-transform:scale(-1,1);transform:scale(-1,1)}.icon-flip-vertical:before{-webkit-transform:scale(1,-1);-moz-transform:scale(1,-1);-ms-transform:scale(1,-1);-o-transform:scale(1,-1);transform:scale(1,-1)}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before,.icon-rotate-right:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before,.icon-mail-forward:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before,.icon-rotate-left:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-reply:before,.icon-mail-reply:before{content:"\f112"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"}.icon-expand-alt:before{content:"\f116"}.icon-collapse-alt:before{content:"\f117"}.icon-smile:before{content:"\f118"}.icon-frown:before{content:"\f119"}.icon-meh:before{content:"\f11a"}.icon-gamepad:before{content:"\f11b"}.icon-keyboard:before{content:"\f11c"}.icon-flag-alt:before{content:"\f11d"}.icon-flag-checkered:before{content:"\f11e"}.icon-terminal:before{content:"\f120"}.icon-code:before{content:"\f121"}.icon-reply-all:before{content:"\f122"}.icon-mail-reply-all:before{content:"\f122"}.icon-star-half-full:before,.icon-star-half-empty:before{content:"\f123"}.icon-location-arrow:before{content:"\f124"}.icon-crop:before{content:"\f125"}.icon-code-fork:before{content:"\f126"}.icon-unlink:before{content:"\f127"}.icon-question:before{content:"\f128"}.icon-info:before{content:"\f129"}.icon-exclamation:before{content:"\f12a"}.icon-superscript:before{content:"\f12b"}.icon-subscript:before{content:"\f12c"}.icon-eraser:before{content:"\f12d"}.icon-puzzle-piece:before{content:"\f12e"}.icon-microphone:before{content:"\f130"}.icon-microphone-off:before{content:"\f131"}.icon-shield:before{content:"\f132"}.icon-calendar-empty:before{content:"\f133"}.icon-fire-extinguisher:before{content:"\f134"}.icon-rocket:before{content:"\f135"}.icon-maxcdn:before{content:"\f136"}.icon-chevron-sign-left:before{content:"\f137"}.icon-chevron-sign-right:before{content:"\f138"}.icon-chevron-sign-up:before{content:"\f139"}.icon-chevron-sign-down:before{content:"\f13a"}.icon-html5:before{content:"\f13b"}.icon-css3:before{content:"\f13c"}.icon-anchor:before{content:"\f13d"}.icon-unlock-alt:before{content:"\f13e"}.icon-bullseye:before{content:"\f140"}.icon-ellipsis-horizontal:before{content:"\f141"}.icon-ellipsis-vertical:before{content:"\f142"}.icon-rss-sign:before{content:"\f143"}.icon-play-sign:before{content:"\f144"}.icon-ticket:before{content:"\f145"}.icon-minus-sign-alt:before{content:"\f146"}.icon-check-minus:before{content:"\f147"}.icon-level-up:before{content:"\f148"}.icon-level-down:before{content:"\f149"}.icon-check-sign:before{content:"\f14a"}.icon-edit-sign:before{content:"\f14b"}.icon-external-link-sign:before{content:"\f14c"}.icon-share-sign:before{content:"\f14d"}