ts-admin 0.2.0

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