├── log
└── README
├── views
├── log
│ ├── README
│ ├── frigga.log.20130502
│ ├── frigga.log
│ └── frigga.log.20130501
├── deploy
│ └── README
├── conf
│ ├── ip.yml
│ ├── base.god
│ ├── frigga.yml
│ ├── frigga.god
│ └── passport-agent.god.sample
├── vendor
│ └── cache
│ │ ├── rack-1.5.2.gem
│ │ ├── thin-1.5.1.gem
│ │ ├── tilt-1.3.7.gem
│ │ ├── tins-0.7.2.gem
│ │ ├── thor-0.18.1.gem
│ │ ├── builder-3.2.0.gem
│ │ ├── daemons-1.1.9.gem
│ │ ├── net-ldap-0.3.1.gem
│ │ ├── rack-rpc-0.0.6.gem
│ │ ├── sinatra-1.4.2.gem
│ │ ├── addressable-2.3.3.gem
│ │ ├── file-tail-1.0.12.gem
│ │ ├── god-xiaomi-1.13.7.gem
│ │ ├── em-websocket-0.3.8.gem
│ │ ├── eventmachine-1.0.3.gem
│ │ ├── rack-protection-1.5.0.gem
│ │ └── sinatra-websocket-0.3.0.gem
├── static
│ └── vendor
│ │ └── bootstrap
│ │ ├── img
│ │ ├── glyphicons-halflings.png
│ │ └── glyphicons-halflings-white.png
│ │ ├── js
│ │ ├── bootstrap-tooltip.js
│ │ └── bootstrap.min.js
│ │ └── css
│ │ └── bootstrap-responsive.min.css
├── notfound.erb
├── error.erb
├── login_form.erb
├── Gemfile
├── lib
│ ├── frigga
│ │ ├── log.rb
│ │ ├── rpc
│ │ │ ├── god.rb
│ │ │ ├── ctmgr.rb
│ │ │ ├── server.rb
│ │ │ └── deploy.rb
│ │ ├── rpc.rb
│ │ ├── auth.rb
│ │ └── talk.rb
│ └── frigga.rb
├── about.erb
├── README.md
├── bin
│ ├── frigga.rb
│ └── testrpc.rb
├── tail.erb
├── layout.erb
├── index.erb
├── ctmgr.erb
└── script
│ └── run.rb
├── conf
├── ip.yml
├── frigga.yml
├── base.god
└── frigga.god
├── .gitignore
├── vendor
└── cache
│ ├── rack-1.5.2.gem
│ ├── thin-1.5.1.gem
│ ├── thor-0.18.1.gem
│ ├── tilt-1.3.7.gem
│ ├── tins-0.7.2.gem
│ ├── builder-3.2.0.gem
│ ├── daemons-1.1.9.gem
│ ├── sinatra-1.4.2.gem
│ ├── file-tail-1.0.12.gem
│ ├── rack-rpc-0.0.6.gem
│ ├── addressable-2.3.3.gem
│ ├── em-websocket-0.3.8.gem
│ ├── eventmachine-1.0.3.gem
│ ├── god-xiaomi-1.13.7.gem
│ ├── rack-protection-1.5.0.gem
│ └── sinatra-websocket-0.3.0.gem
├── static
└── vendor
│ └── bootstrap
│ ├── img
│ ├── glyphicons-halflings.png
│ └── glyphicons-halflings-white.png
│ ├── js
│ ├── bootstrap-tooltip.js
│ └── bootstrap.min.js
│ └── css
│ └── bootstrap-responsive.min.css
├── Gemfile
├── lib
├── frigga
│ ├── rpc
│ │ ├── server.rb
│ │ └── god.rb
│ ├── log.rb
│ ├── auth.rb
│ ├── rpc.rb
│ └── talk.rb
└── frigga.rb
├── bin
└── frigga.rb
├── script
└── run.rb
└── README.md
/log/README:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/views/log/README:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/views/deploy/README:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/conf/ip.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - 127.0.0.1
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | gods/
2 | log/frigga.*
3 | Gemfile.lock
4 |
--------------------------------------------------------------------------------
/conf/frigga.yml:
--------------------------------------------------------------------------------
1 | ---
2 | port: 9001
3 | http_auth: ["admin", "123"]
4 |
--------------------------------------------------------------------------------
/views/conf/ip.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - 172.30.251.110
3 | - 10.0.9.10
4 | - 192.168.1.106
5 |
--------------------------------------------------------------------------------
/vendor/cache/rack-1.5.2.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/rack-1.5.2.gem
--------------------------------------------------------------------------------
/vendor/cache/thin-1.5.1.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/thin-1.5.1.gem
--------------------------------------------------------------------------------
/vendor/cache/thor-0.18.1.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/thor-0.18.1.gem
--------------------------------------------------------------------------------
/vendor/cache/tilt-1.3.7.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/tilt-1.3.7.gem
--------------------------------------------------------------------------------
/vendor/cache/tins-0.7.2.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/tins-0.7.2.gem
--------------------------------------------------------------------------------
/vendor/cache/builder-3.2.0.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/builder-3.2.0.gem
--------------------------------------------------------------------------------
/vendor/cache/daemons-1.1.9.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/daemons-1.1.9.gem
--------------------------------------------------------------------------------
/vendor/cache/sinatra-1.4.2.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/sinatra-1.4.2.gem
--------------------------------------------------------------------------------
/vendor/cache/file-tail-1.0.12.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/file-tail-1.0.12.gem
--------------------------------------------------------------------------------
/vendor/cache/rack-rpc-0.0.6.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/rack-rpc-0.0.6.gem
--------------------------------------------------------------------------------
/views/vendor/cache/rack-1.5.2.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/rack-1.5.2.gem
--------------------------------------------------------------------------------
/views/vendor/cache/thin-1.5.1.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/thin-1.5.1.gem
--------------------------------------------------------------------------------
/views/vendor/cache/tilt-1.3.7.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/tilt-1.3.7.gem
--------------------------------------------------------------------------------
/views/vendor/cache/tins-0.7.2.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/tins-0.7.2.gem
--------------------------------------------------------------------------------
/vendor/cache/addressable-2.3.3.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/addressable-2.3.3.gem
--------------------------------------------------------------------------------
/vendor/cache/em-websocket-0.3.8.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/em-websocket-0.3.8.gem
--------------------------------------------------------------------------------
/vendor/cache/eventmachine-1.0.3.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/eventmachine-1.0.3.gem
--------------------------------------------------------------------------------
/vendor/cache/god-xiaomi-1.13.7.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/god-xiaomi-1.13.7.gem
--------------------------------------------------------------------------------
/views/vendor/cache/thor-0.18.1.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/thor-0.18.1.gem
--------------------------------------------------------------------------------
/vendor/cache/rack-protection-1.5.0.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/rack-protection-1.5.0.gem
--------------------------------------------------------------------------------
/views/vendor/cache/builder-3.2.0.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/builder-3.2.0.gem
--------------------------------------------------------------------------------
/views/vendor/cache/daemons-1.1.9.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/daemons-1.1.9.gem
--------------------------------------------------------------------------------
/views/vendor/cache/net-ldap-0.3.1.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/net-ldap-0.3.1.gem
--------------------------------------------------------------------------------
/views/vendor/cache/rack-rpc-0.0.6.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/rack-rpc-0.0.6.gem
--------------------------------------------------------------------------------
/views/vendor/cache/sinatra-1.4.2.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/sinatra-1.4.2.gem
--------------------------------------------------------------------------------
/vendor/cache/sinatra-websocket-0.3.0.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/vendor/cache/sinatra-websocket-0.3.0.gem
--------------------------------------------------------------------------------
/views/vendor/cache/addressable-2.3.3.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/addressable-2.3.3.gem
--------------------------------------------------------------------------------
/views/vendor/cache/file-tail-1.0.12.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/file-tail-1.0.12.gem
--------------------------------------------------------------------------------
/views/vendor/cache/god-xiaomi-1.13.7.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/god-xiaomi-1.13.7.gem
--------------------------------------------------------------------------------
/views/vendor/cache/em-websocket-0.3.8.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/em-websocket-0.3.8.gem
--------------------------------------------------------------------------------
/views/vendor/cache/eventmachine-1.0.3.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/eventmachine-1.0.3.gem
--------------------------------------------------------------------------------
/views/vendor/cache/rack-protection-1.5.0.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/rack-protection-1.5.0.gem
--------------------------------------------------------------------------------
/views/vendor/cache/sinatra-websocket-0.3.0.gem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/vendor/cache/sinatra-websocket-0.3.0.gem
--------------------------------------------------------------------------------
/static/vendor/bootstrap/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/static/vendor/bootstrap/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/conf/base.god:
--------------------------------------------------------------------------------
1 | God::Contacts::Email.defaults do |d|
2 | d.from_email = 'god@xiaomi.com'
3 | d.from_name = 'God'
4 | d.delivery_method = :sendmail
5 | end
6 |
--------------------------------------------------------------------------------
/static/vendor/bootstrap/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/static/vendor/bootstrap/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/views/conf/base.god:
--------------------------------------------------------------------------------
1 | God::Contacts::Email.defaults do |d|
2 | d.from_email = 'god@xiaomi.com'
3 | d.from_name = 'God'
4 | d.delivery_method = :sendmail
5 | end
6 |
--------------------------------------------------------------------------------
/views/static/vendor/bootstrap/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/static/vendor/bootstrap/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/views/notfound.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | 错误! 页面不存在.
4 |
--------------------------------------------------------------------------------
/views/error.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | 错误! <%= @error %>
4 |
5 |
--------------------------------------------------------------------------------
/views/static/vendor/bootstrap/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaomi-sa/frigga/HEAD/views/static/vendor/bootstrap/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | #gem 'god', :git => 'git://github.com/WilburJ/god-xiaomi.git'
2 | gem 'god-xiaomi'
3 | gem 'rack-rpc'
4 | gem 'sinatra'
5 | gem 'thin'
6 | gem 'thor'
7 | gem 'file-tail'
8 | gem 'sinatra-websocket'
9 |
--------------------------------------------------------------------------------
/views/login_form.erb:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/views/conf/frigga.yml:
--------------------------------------------------------------------------------
1 | ---
2 | port: 9001
3 |
4 | white_list:
5 | - 127.0.0.1
6 | - 10.0.0.1
7 |
8 | ldap:
9 | host: 10.0.0.1
10 | port: 389
11 | base: OU=xxx,DC=xxx,DC=net
12 | username: user
13 | password: pass
14 |
--------------------------------------------------------------------------------
/views/Gemfile:
--------------------------------------------------------------------------------
1 | #gem 'god', :git => 'git://github.com/WilburJ/god-xiaomi.git'
2 | gem 'god-xiaomi'
3 | gem 'rack-rpc'
4 | gem 'sinatra'
5 | gem 'thin'
6 | gem 'thor'
7 | gem 'net-ldap'
8 | gem 'file-tail'
9 | gem 'sinatra-websocket'
10 |
--------------------------------------------------------------------------------
/lib/frigga/rpc/server.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module RPC
3 | module Server
4 | RPC_LIST = %w(ver)
5 | def ver
6 | VER
7 | end
8 |
9 | end # module Server finish
10 | end # module RPC finish
11 | end # module Frigga finish
12 |
--------------------------------------------------------------------------------
/lib/frigga/log.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | require 'logger'
3 | require 'singleton'
4 | class Log
5 | include Singleton
6 | attr_accessor :logger
7 | def initialize
8 | @logger = Logger.new('log/frigga.log', 'daily', 10)
9 | @logger.formatter = proc { |severity, datetime, progname, msg| "#{datetime} #{severity} -- : #{msg}\n"}
10 | @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
11 | @logger.level = eval("Logger::#{LOG_LEVEL.upcase}")
12 | end
13 | end
14 | Logger = Log.instance.logger
15 |
16 | end
--------------------------------------------------------------------------------
/views/lib/frigga/log.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | require 'logger'
3 | require 'singleton'
4 | class Log
5 | include Singleton
6 | attr_accessor :logger
7 | def initialize
8 | @logger = Logger.new('log/frigga.log', 'daily', 10)
9 | @logger.formatter = proc { |severity, datetime, progname, msg| "#{datetime} #{severity} -- : #{msg}\n"}
10 | @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
11 | @logger.level = eval("Logger::#{LOG_LEVEL.upcase}")
12 | end
13 | end
14 | Logger = Log.instance.logger
15 |
16 | end
--------------------------------------------------------------------------------
/lib/frigga/auth.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module Auth
3 | require 'open-uri'
4 | def authorized?
5 | @auth ||= Rack::Auth::Basic::Request.new(request.env)
6 | @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [Http_auth_user, Http_auth_passwd]
7 | end
8 |
9 | def protected!
10 | unless authorized?
11 | response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
12 | throw(:halt, [401, "Oops... we need your login name & password\n"])
13 | end
14 | end
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/conf/frigga.god:
--------------------------------------------------------------------------------
1 | frigga_path = File.expand_path("./bin")
2 | God.watch do |w|
3 | w.name = "frigga"
4 | w.start = "ruby #{frigga_path}/frigga.rb"
5 | w.keepalive(:memory_max => 100.megabytes, :cpu_max => 50.percent)
6 | w.behavior(:clean_pid_file)
7 |
8 | w.lifecycle do |on|
9 | on.condition(:flapping) do |c|
10 | c.to_state = [:start, :restart]
11 | c.times = 3
12 | c.within = 3.minute
13 | c.transition = :unmonitored
14 | c.retry_in = 10.minutes
15 | c.retry_times = 2
16 | c.retry_within = 1.hours
17 | end
18 | end
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/views/conf/frigga.god:
--------------------------------------------------------------------------------
1 | frigga_path = File.expand_path("./bin")
2 | God.watch do |w|
3 | w.name = "frigga"
4 | w.start = "ruby #{frigga_path}/frigga.rb"
5 | w.keepalive(:memory_max => 100.megabytes, :cpu_max => 50.percent)
6 | w.behavior(:clean_pid_file)
7 |
8 | w.lifecycle do |on|
9 | on.condition(:flapping) do |c|
10 | c.to_state = [:start, :restart]
11 | c.times = 3
12 | c.within = 3.minute
13 | c.transition = :unmonitored
14 | c.retry_in = 10.minutes
15 | c.retry_times = 2
16 | c.retry_within = 1.hours
17 | end
18 | end
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/lib/frigga/rpc/god.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module RPC
3 | module God
4 | #must have RPC_LIST for regsiter rpc_call
5 | RPC_LIST = %w(status restart start stop)
6 | def status
7 | hi = Frigga::Talk.god('status')
8 | hi.map {|k, v| [k, v[:status], v[:start_time], v[:pid], v[:start]] }
9 | end
10 | def restart(str)
11 | Frigga::Talk.god('restart', str)
12 | end
13 |
14 | def start(str)
15 | Frigga::Talk.god('start', str)
16 | end
17 |
18 | def stop(str)
19 | Frigga::Talk.god('stop', str)
20 | end
21 |
22 | end #God
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/views/lib/frigga/rpc/god.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module RPC
3 | module God
4 | #must have RPC_LIST for regsiter rpc_call
5 | RPC_LIST = %w(status restart start stop)
6 | def status
7 | hi = Frigga::Talk.god('status')
8 | hi.map {|k, v| [k, v[:status], v[:start_time], v[:pid], v[:start]] }
9 | end
10 | def restart(str)
11 | Frigga::Talk.god('restart', str)
12 | end
13 |
14 | def start(str)
15 | Frigga::Talk.god('start', str)
16 | end
17 |
18 | def stop(str)
19 | Frigga::Talk.god('stop', str)
20 | end
21 |
22 | end #God
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/views/log/frigga.log.20130502:
--------------------------------------------------------------------------------
1 | # Logfile created on 2013-05-02 23:56:06 +0800 by logger.rb/31641
2 | 2013-05-02 23:56:06 +0800 INFO -- : [192.168.1.106] /
3 | 2013-05-02 23:56:06 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
4 | 2013-05-02 23:56:07 +0800 INFO -- : [192.168.1.106] /favicon.ico
5 | 2013-05-02 23:56:07 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
6 | 2013-05-02 23:56:13 +0800 INFO -- : [192.168.1.106] /ctmgr
7 | 2013-05-02 23:56:13 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
8 | 2013-05-02 23:56:13 +0800 INFO -- : [192.168.1.106] /favicon.ico
9 | 2013-05-02 23:56:13 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
10 |
--------------------------------------------------------------------------------
/views/about.erb:
--------------------------------------------------------------------------------
1 | Frigga 版本: <%= @ver %>
2 |
3 |
4 |
5 | | Process |
6 | Status |
7 | Start Time |
8 |
9 |
11 |
12 | |
13 | Frigga
14 | |
15 |
16 | <% if @process['frigga'][:status] == 'running' %>
17 | <%= @process['frigga'][:status] %> |
18 | <% else %>
19 | <%= @process['frigga'][:status] %>
20 | <% end %>
21 | <%= @process['frigga'][:start_time] %> |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/views/conf/passport-agent.god.sample:
--------------------------------------------------------------------------------
1 | God.watch do |w|
2 | w.name = "passport-agent"
3 | w.start = "/home/work/opdir/passport/release/run.sh"
4 | w.log = "/home/work/opdir/passport/release/run.log"
5 | w.dir = "/home/work/opdir/passport/release"
6 | w.env = {"PATH"=>"/home/work/jdk/bin/:/bin:/usr/bin:/sbin", "JAVA_HOME"=>"/home/work/jdk", "CLASSPATH"=>".:/home/work/jdk/lib/tools.jar:/home/work/jdk/lib/rt.jar"}
7 | w.keepalive(
8 |
9 |
10 | )
11 | w.behavior(:clean_pid_file)
12 | w.stop_timeout = 60.seconds
13 | w.process_log = "/home/work/opdir/passport.yaml"
14 |
15 | w.lifecycle do |on|
16 | on.condition(:flapping) do |c|
17 | c.to_state = [:start, :restart]
18 | c.times = 3
19 | c.within = 10.minute
20 | c.transition = :unmonitored
21 | c.retry_in = 20.minutes
22 | c.retry_times = 2
23 | c.retry_within = 1.hours
24 | c.notify = 'proc_down'
25 | end
26 | end
27 |
28 | end
29 |
30 | God.contact(:email) do |c|
31 | c.name = 'proc_down'
32 | c.group = 'developers'
33 |
34 | c.to_email = "fangshaosen@xiaomi.com"
35 |
36 | end
37 |
38 |
--------------------------------------------------------------------------------
/views/README.md:
--------------------------------------------------------------------------------
1 | ## 介绍
2 |
3 | Frigga是一个针对程序部署以及监控的自动化工具
4 |
5 | 在北欧神话中,frigga是神后,odin的妻子;掌管婚姻和家庭;负责纺织云彩
6 |
7 | frigga是部署的client端,相配合的是odin,部署的控制端。在一次部署任务中frigga负责调用thor进行部署
8 | 在部署功能中,frigga的上游系统是odin,下游系统是thor
9 |
10 | ## 功能:
11 |
12 | - 作为client端发起一次程序部署
13 | - 提供xmlrpc接口来进行部署任务的查询
14 | - 集成了god,用来作为程序的supervise
15 | - 支持web化的god
16 | - 支持日志
17 | - 支持添加自定义的xmlrpc接口
18 | - 支持自升级
19 |
20 |
21 | ## 环境依赖
22 |
23 | - Ruby 1.9.3
24 |
25 | ## 安装
26 |
27 | ```
28 | git clone
29 | ```
30 |
31 | ## 使用
32 |
33 | ### 基本用法
34 |
35 |
36 | `启动frigga god 以及需要启动的supervise程序`
37 |
38 |
39 | ```
40 | cd script/ && ./run.rb start
41 | ```
42 |
43 |
44 | `用god管理进程`
45 |
46 |
47 | ```
48 | god start/stop/restart frigga
49 | ```
50 |
51 |
52 | ### 使用god管理进程
53 |
54 | `添加god配置文件到 conf/ 目录下, 配置文件以.god作为后缀`
55 |
56 |
57 | ### 为frigga添加白名单
58 |
59 | `在conf/下添加ip.yml文件,将需要信任的机器加入`
60 |
61 |
62 | ```
63 | ---
64 | - 10.237.37.23
65 | - 10.237.37.24
66 | ```
67 |
68 | ### 高级功能
69 |
70 |
71 | `添加自定义xmlrpc接口`
72 |
73 |
74 | ## Halp!
75 | 联系 xiedanbo <xiedanbo@xiaomi.com>
76 |
--------------------------------------------------------------------------------
/views/bin/frigga.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #ecoding: utf-8
3 | $: << "../lib" << "./lib"
4 |
5 | require "pathname"
6 | require 'yaml'
7 |
8 | Dir.chdir Pathname.new(__FILE__).realpath + "../.."
9 |
10 | DIR = File.expand_path("")
11 |
12 | def load_yml(yml_path)
13 | yaml_context = nil
14 | begin
15 | yaml_context = YAML::load_file(yml_path) if File.exist?(yml_path)
16 | rescue Exception => e
17 | abort "Read yaml file[#{yml_path}] error! -> #{e}"
18 | end
19 | yaml_context
20 | end
21 |
22 | #log white_ip from ip.yml
23 | if File.exist?("conf/ip.yml")
24 | white_ip = load_yml("conf/ip.yml")
25 | abort "ip.yml must be array!" unless white_ip.kind_of?(Array)
26 | end
27 | #load main config
28 | conf = load_yml("conf/frigga.yml")
29 | #http-server port
30 | Http_port = conf.fetch "port", 9001
31 | #http-server ip white_list
32 | White_list = (conf.fetch "white_list", []) + (white_ip.nil? ? [] : white_ip)
33 | #ldap config
34 | Ldap = conf.fetch "ldap", {}
35 |
36 |
37 | #god's sock
38 | GOD_SOCK = "/tmp/god.17165.sock"
39 | #log level: debug > info > warn > fatal
40 | LOG_LEVEL = 'info'
41 | VER = '0.0.5'
42 |
43 | require 'frigga'
44 | #ok, let's go
45 | Frigga::WebServer.run!
46 |
--------------------------------------------------------------------------------
/views/lib/frigga/rpc/ctmgr.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 |
3 | module RPC
4 |
5 | require "digest/md5"
6 | require "fileutils"
7 | require "pathname"
8 |
9 | BASE_PATH = Pathname.new(File.dirname(__FILE__)).realpath.to_s.split("/")[0..-4].join("/") + "/deploy/"
10 | CT_USERS = ["root", "work"]
11 |
12 | module Ctmgr
13 |
14 | RPC_LIST = %w( getCtstatus )
15 |
16 | def getCtstatus
17 | ct_all = []
18 |
19 | CT_USERS.each do |u|
20 | ct_detail = `crontab -l -u #{u}`.split("\n")
21 | counter = 1
22 | if ct_detail.empty? or ct_detail =~ /^no crontab for/
23 | ct_all << ["user : #{u}", "no crontab for #{u}"]
24 | else
25 | ct_array = ["user : #{u}"]
26 | ct_detail.each do |i|
27 | if i =~ /^#/
28 | ct_array << i
29 | else
30 | ct_array << [counter, i]
31 | counter += 1
32 | end
33 | end
34 | ct_all << ct_array
35 | end
36 | end
37 | return ct_all
38 | end
39 |
40 |
41 | end # end of Ctmgr module
42 | end # end of RPC module
43 | end # end of Frigga module
44 |
--------------------------------------------------------------------------------
/bin/frigga.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #ecoding: utf-8
3 | $: << "../lib" << "./lib"
4 |
5 | require "pathname"
6 | require 'yaml'
7 |
8 | Dir.chdir Pathname.new(__FILE__).realpath + "../.."
9 |
10 | DIR = File.expand_path("")
11 |
12 | def load_yml(yml_path)
13 | yaml_context = nil
14 | begin
15 | yaml_context = YAML::load_file(yml_path) if File.exist?(yml_path)
16 | rescue Exception => e
17 | abort "Read yaml file[#{yml_path}] error! -> #{e}"
18 | end
19 | yaml_context
20 | end
21 |
22 | #log white_ip from ip.yml
23 | if File.exist?("conf/ip.yml")
24 | white_ip = load_yml("conf/ip.yml")
25 | abort "ip.yml must be array!" unless white_ip.kind_of?(Array)
26 | end
27 | #load main config
28 | conf = load_yml("conf/frigga.yml")
29 | #http-server basic auth
30 | Http_auth_user, Http_auth_passwd = conf.fetch "http_auth", nil
31 | #http-server port
32 | Http_port = conf.fetch "port", 9001
33 | #http-server ip white_list
34 | White_list = (white_ip.nil? ? [] : white_ip)
35 | #ldap config
36 | Ldap = conf.fetch "ldap", {}
37 |
38 |
39 | #god's sock
40 | GOD_SOCK = "/tmp/god.17165.sock"
41 | #log level: debug > info > warn > fatal
42 | LOG_LEVEL = 'info'
43 | VER = '1.0.0'
44 |
45 | require 'frigga'
46 | #ok, let's go
47 | Frigga::WebServer.run!
48 |
--------------------------------------------------------------------------------
/views/tail.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 注意: 点击stop按钮停止tail log,继续tail log请刷新本页面
5 |
6 |
7 |
8 |
9 |
13 |
14 |
42 |
--------------------------------------------------------------------------------
/views/layout.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Frigga 进程管理 <%= @ver %>
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
40 |
41 |
42 |
43 | <%unless @msg.nil? %>
44 |
45 |
<%= @msg %>
46 | <% end %>
47 |
48 |
49 |
50 | <%= yield %>
51 |
52 |
53 |
--------------------------------------------------------------------------------
/lib/frigga/rpc.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module RPC
3 | require 'rack/rpc'
4 | require 'sinatra/base'
5 | Dir.glob(File.join(File.dirname(__FILE__), 'rpc', "*.rb")) do |file|
6 | require file
7 | end
8 | class Runner < Rack::RPC::Server
9 | #include rpc/*.rb and regsiter rpc call
10 | #eg. rpc/god.rb god.hello
11 | @@rpc_list = []
12 | Dir.glob(File.join(File.dirname(__FILE__), 'rpc', "*.rb")) do |file|
13 | rpc_class = File.basename(file).split('.rb')[0].capitalize
14 | rpc_list = []
15 | eval "include Frigga::RPC::#{rpc_class}"
16 | eval "rpc_list = Frigga::RPC::#{rpc_class}::RPC_LIST"
17 | rpc_list.each do |rpc_name|
18 | eval "alias :old_#{rpc_class.downcase}_#{rpc_name} :#{rpc_name}"
19 | define_method "#{rpc_class.downcase}_#{rpc_name}".to_sym do |*arg|
20 | Logger.info "[#{request.ip}] called #{rpc_class.downcase}.#{rpc_name} #{arg.join(', ')}"
21 | eval "old_#{rpc_class.downcase}_#{rpc_name} *arg"
22 | end
23 | rpc "#{rpc_class.downcase}.#{rpc_name}" => "#{rpc_class.downcase}_#{rpc_name}".to_sym
24 | @@rpc_list << "#{rpc_class.downcase}.#{rpc_name}"
25 | end
26 | end
27 |
28 | def help
29 | rpc_methods = (['help'] + @@rpc_list.sort).join("\n")
30 | end
31 | rpc "help" => :help
32 |
33 | before_filter :check_auth
34 |
35 | def check_auth
36 | unless White_list.include?(request.ip)
37 | Logger.info "[#{request.ip}] Not authorized"
38 | raise "Not authorized"
39 | end
40 | end
41 |
42 | end
43 | end #RPC
44 | end
45 |
--------------------------------------------------------------------------------
/views/lib/frigga/rpc.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module RPC
3 | require 'rack/rpc'
4 | require 'sinatra/base'
5 | Dir.glob(File.join(File.dirname(__FILE__), 'rpc', "*.rb")) do |file|
6 | require file
7 | end
8 | class Runner < Rack::RPC::Server
9 | #include rpc/*.rb and regsiter rpc call
10 | #eg. rpc/god.rb god.hello
11 | @@rpc_list = []
12 | Dir.glob(File.join(File.dirname(__FILE__), 'rpc', "*.rb")) do |file|
13 | rpc_class = File.basename(file).split('.rb')[0].capitalize
14 | rpc_list = []
15 | eval "include Frigga::RPC::#{rpc_class}"
16 | eval "rpc_list = Frigga::RPC::#{rpc_class}::RPC_LIST"
17 | rpc_list.each do |rpc_name|
18 | eval "alias :old_#{rpc_class.downcase}_#{rpc_name} :#{rpc_name}"
19 | define_method "#{rpc_class.downcase}_#{rpc_name}".to_sym do |*arg|
20 | Logger.info "[#{request.ip}] called #{rpc_class.downcase}.#{rpc_name} #{arg.join(', ')}"
21 | eval "old_#{rpc_class.downcase}_#{rpc_name} *arg"
22 | end
23 | rpc "#{rpc_class.downcase}.#{rpc_name}" => "#{rpc_class.downcase}_#{rpc_name}".to_sym
24 | @@rpc_list << "#{rpc_class.downcase}.#{rpc_name}"
25 | end
26 | end
27 |
28 | def help
29 | rpc_methods = (['help'] + @@rpc_list.sort).join("\n")
30 | end
31 | rpc "help" => :help
32 |
33 | before_filter :check_auth
34 |
35 | def check_auth
36 | unless White_list.include?(request.ip)
37 | Logger.info "[#{request.ip}] Not authorized"
38 | raise "Not authorized"
39 | end
40 | end
41 |
42 | end
43 | end #RPC
44 | end
45 |
--------------------------------------------------------------------------------
/views/bin/testrpc.rb:
--------------------------------------------------------------------------------
1 | $: << 'lib' << '../lib'
2 | require "rubygems"
3 | require "xmlrpc/client"
4 | require "pp"
5 |
6 | #server = XMLRPC::Client.new2("http://foo:bar@0.0.0.0:9001/rpc")
7 | server = XMLRPC::Client.new2("http://127.0.0.1:9001/rpc")
8 | #result = server.call('system.listMethods')
9 | #result = server.call('supervisor.getAllProcessInfo')
10 | #result = server.call('deployment.getAllStatus')
11 | #result = server.call('help')
12 | puts server.call('help')
13 | puts "-----------------------"
14 | loop do
15 | begin
16 | x = gets.strip.split(/\s/)
17 | next if x.empty?
18 | result = server.call(*x)
19 | pp result
20 | puts "-----------------------"
21 | rescue => e
22 | puts e.message
23 | puts e.backtrace.join("\n")
24 | next
25 | end
26 | end
27 | #result = server.call('help')
28 | #puts result
29 | #result = server.call('god.status')
30 | #result = server.call('deploy.startDeploy', '8081', 'first_deploy-1.0.0.0.tar.gz')
31 | #puts result
32 |
33 | __END__
34 | options = {
35 | :backtrace => true,
36 | :dir_mode => :script,
37 | :dir => 'pids',
38 | :monitor => true
39 | }
40 |
41 | current_dir = Dir.pwd
42 |
43 | Daemons.run_proc('tower', options) do
44 | loop do
45 | Dir.chdir(current_dir)
46 | logger = Log.instance
47 | EM.run do
48 | # hit Control + C to stop
49 | # Signal.trap("INT") { EM.stop }
50 | # Signal.trap("TERM") { EM.stop }
51 |
52 | #load conf file
53 | $conf = load_config('../conf/dm-server.yaml')
54 | EM.stop if $conf.nil?
55 |
56 | $logger.info "start dm-server..."
57 | EM.start_server($conf['ip'], $conf['port'], DomainReportServer)
58 | $DB = Sequel.connect($conf['db'], :max_connections => 10)
59 | end
60 | sleep 3
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/views/lib/frigga/auth.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module Auth
3 | require 'net/ldap'
4 | require 'open-uri'
5 | def authorized?
6 | @auth ||= Rack::Auth::Basic::Request.new(request.env)
7 | @auth.provided? && @auth.basic? && @auth.credentials && check_ldap
8 | end
9 |
10 | def protected!
11 | unless authorized?
12 | response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
13 | throw(:halt, [401, "Oops... we need your login name & password\n"])
14 | end
15 | end
16 |
17 | def check_ldap
18 | ldap = Net::LDAP.new({ :host => Ldap['host'],
19 | :port => Ldap['port'],
20 | :base => Ldap['base'],
21 | :auth => { :method => :simple,
22 | :username => Ldap['username'],
23 | :password => Ldap['password']
24 | }
25 | })
26 |
27 | filter = Net::LDAP::Filter.eq("mail", @auth.credentials[0])
28 | ldap_entry = nil
29 | ldap.search(:filter => filter) {|entry| ldap_entry = entry}
30 | return false if ldap_entry.nil?
31 | ldap.auth(ldap_entry.dn, @auth.credentials[1])
32 | if ldap.bind
33 | door_god = get_doorgod_list(@auth.credentials[0].split('@')[0])
34 | ip_list = `ip -f inet addr | grep global | awk '{print $2}' | awk -F/ '{print $1}'`.split("\n").select {|f| f =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d+{1,3}/}
35 | return ip_list.map {|f| door_god.scan("(#{f})").empty? ? false : true}.any?
36 | else
37 | return false
38 | end
39 | end #check_ldap
40 |
41 | def get_doorgod_list(user)
42 | host_list = open "http://krb1.xiaomi.net/getmyinfo.php?user=#{user}" do |f|
43 | f.read
44 | end
45 | end
46 |
47 | end
48 | end
--------------------------------------------------------------------------------
/script/run.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #ecoding: utf-8
3 | require "pathname"
4 |
5 | Dir.chdir Pathname.new(__FILE__).realpath + "../.."
6 |
7 | # update gem
8 |
9 | gem_lock =Pathname.new(File.dirname(__FILE__)).realpath.to_s.split("/")[0..-2].join("/") + "/Gemfile.lock"
10 | unless File.exist?(gem_lock)
11 | `bundle update --local`
12 | abort "Install gem failed!" if $? != 0
13 | end
14 |
15 | DIR = File.expand_path("")
16 | abort "God does not exist..." unless system("which god > /dev/null")
17 |
18 | require "thor"
19 | class Cli < Thor
20 | desc "start", "Start God, Frigga and #{DIR}/gods/*.god"
21 | def start
22 | #wake up god
23 | wakeup_god = %W(god --no-events --log-level info -c #{DIR}/conf/base.god)
24 | abort "Start God failed!" unless system *wakeup_god
25 |
26 | #use god to load frigga.god for wakeing frigga up
27 | wakeup_frigga = "god load conf/frigga.god"
28 | abort "Start Frigga failed!" unless system *wakeup_frigga
29 |
30 | #start process
31 | Dir.glob(File.join(Dir.pwd, 'gods', "*.god")) do |god|
32 | start_process = "god load #{god}"
33 | `#{start_process}`
34 | warn "Start process[#{god}] failed!" unless $? == 0
35 | end
36 |
37 | #check process status
38 | puts "Command: god status"
39 | system("god status")
40 | end
41 |
42 | desc "stop", "Stop God and Frigga, Don't stop *.god"
43 | def stop
44 | #stop frigg
45 | stop_frigg = "god stop frigga"
46 | `#{stop_frigg}`
47 | warn "Stop Frigga failed!" unless $? == 0
48 |
49 | #stop god
50 | stop_god = %W(god quit)
51 | abort "Stop God failed!" unless system *stop_god
52 | end
53 |
54 | desc "nuke", "Stop God,Frigga and *.god"
55 | def nuke
56 | #terminate god
57 | nuke_god = %W(god terminate)
58 | abort "Nuke all gods failed!" unless system *nuke_god
59 | end
60 |
61 | desc "stop_god", "Stop God only, update god gem."
62 | def stop_god
63 | # update god
64 | stop_god = %W(god quit)
65 | abort "Stop God failed!" unless system *stop_god
66 | end
67 |
68 | end
69 |
70 | Cli.start(ARGV)
71 |
72 |
--------------------------------------------------------------------------------
/lib/frigga/talk.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | require "drb"
3 | class Talk
4 | def self.god(command, task = "")
5 | new.action(command, task)
6 | end
7 |
8 | def initialize
9 | @server = DRbObject.new(nil, "drbunix://#{GOD_SOCK}")
10 | end
11 |
12 | def ping
13 | begin
14 | @server.ping
15 | rescue DRb::DRbConnError
16 | raise "God server is not available, #{GOD_SOCK}"
17 | end
18 | end
19 |
20 | def action(command, task = "")
21 | if %w{status}.include?(command)
22 | ping
23 | status
24 | elsif %w{start stop restart monitor unmonitor remove}.include?(command)
25 | ping
26 | @server.control(task, command)
27 | else
28 | raise "Command '#{command}' is not valid."
29 | end
30 | end
31 |
32 | def status
33 | hi = @server.status
34 | process = {}
35 | unless hi.nil? || hi.empty?
36 | hi.each do |k, v|
37 |
38 | start_time = ""
39 | status = v[:state] == :up ? 'running' : (v[:state] == :unmonitored ? 'stop' : 'something wrong!')
40 | pid = v.fetch :pid, nil
41 | if v[:state] == :up
42 | if ! pid.nil? && File.exist?("/proc/#{pid}")
43 | jiffies = IO.read("/proc//#{pid}/stat").split(/\s/)[21].to_i
44 | uptime = IO.readlines("/proc/stat").find {|t| t =~ /^btime/ }.split(/\s/)[1].strip.to_i
45 | start_time = Time.at(uptime + jiffies / 100).strftime("%Y-%m-%d %H:%M:%S")
46 | else
47 | status = 'flapping'
48 | start_time = "Can't find pid:#{pid}"
49 | end
50 |
51 | end
52 | process[k] = {:status => status,
53 | :start_time => start_time,
54 | :start => v[:start],
55 | :pid => pid,
56 | :http_url => v[:http_url],
57 | :all_log => { :process_log => v[:process_log],
58 | :log => v[:log],
59 | :err_log => v[:err_log]
60 | }
61 | }
62 | end
63 | end
64 | process
65 | end
66 | end #talk
67 |
68 | end
69 |
--------------------------------------------------------------------------------
/views/lib/frigga/talk.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | require "drb"
3 | #require 'singleton'
4 | class Talk
5 | #include Singleton
6 | def self.god(command, task = "")
7 | new.action(command, task)
8 | end
9 |
10 | def initialize
11 | @server = DRbObject.new(nil, "drbunix://#{GOD_SOCK}")
12 | #ping
13 | end
14 |
15 | def ping
16 | begin
17 | @server.ping
18 | rescue DRb::DRbConnError
19 | raise "God server is not available, #{GOD_SOCK}"
20 | end
21 | end
22 |
23 | def action(command, task = "")
24 | if %w{status}.include?(command)
25 | ping
26 | status
27 | elsif %w{start stop restart monitor unmonitor remove}.include?(command)
28 | ping
29 | @server.control(task, command)
30 | else
31 | raise "Command '#{command}' is not valid."
32 | end
33 | end
34 |
35 | def status
36 | hi = @server.status
37 | process = {}
38 | unless hi.nil? || hi.empty?
39 | hi.each do |k, v|
40 |
41 | start_time = ""
42 | status = v[:state] == :up ? 'running' : (v[:state] == :unmonitored ? 'stop' : 'something wrong!')
43 | pid = v.fetch :pid, nil
44 | if v[:state] == :up
45 | if ! pid.nil? && File.exist?("/proc/#{pid}")
46 | jiffies = IO.read("/proc//#{pid}/stat").split(/\s/)[21].to_i
47 | uptime = IO.readlines("/proc/stat").find {|t| t =~ /^btime/ }.split(/\s/)[1].strip.to_i
48 | start_time = Time.at(uptime + jiffies / 100).strftime("%Y-%m-%d %H:%M:%S")
49 | else
50 | status = 'flapping'
51 | start_time = "Can't find pid:#{pid}"
52 | end
53 |
54 | end
55 | process[k] = {:status => status,
56 | :start_time => start_time,
57 | :start => v[:start],
58 | :pid => pid,
59 | :http_url => v[:http_url],
60 | :all_log => { :process_log => v[:process_log],
61 | :log => v[:log],
62 | :err_log => v[:err_log]
63 | }
64 | }
65 | end
66 | end
67 | process
68 | end
69 | end #talk
70 | #Talk_to = Frigga::Talk.instance
71 |
72 | end
73 |
--------------------------------------------------------------------------------
/views/lib/frigga/rpc/server.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 | module RPC
3 | module Server
4 | RPC_LIST = %w(ver update)
5 | def ver
6 | VER
7 | end
8 |
9 | def update
10 | restart_flag = false
11 | if system("which git > /dev/null") && File.directory?(".git")
12 | clean = `git status --porcelain`.empty?
13 | current_branch = `git symbolic-ref HEAD`.chomp
14 | master = current_branch == "refs/heads/master"
15 | no_new_commits = system('git diff --exit-code --quiet origin/master master')
16 |
17 | short_branch = current_branch.split('/').last
18 |
19 | if !master
20 | return ["fail", "Frigga on a non-master branch '#{short_branch}', won't auto-update!"]
21 | elsif !no_new_commits
22 | return ["fail", "Frigga has unpushed commits on master, won't auto-update!"]
23 | elsif !clean
24 | return ["fail", "Frigga has a dirty tree, won't auto-update!"]
25 | end
26 |
27 | if clean && master && no_new_commits
28 | quietly = "> /dev/null 2>&1"
29 | fetch = "(git fetch origin #{quietly})"
30 | reset = "(git reset --hard origin/master #{quietly})"
31 | reclean = "(git clean -df #{quietly})"
32 |
33 | if system "#{fetch}"
34 | origin_log = `git log origin/master -1 --oneline`
35 | master_log = `git log master -1 --oneline`
36 | restart_flag = (origin_log == master_log ? false : true)
37 | else
38 | return ["fail", "Git fetch failed."]
39 | end
40 |
41 | unless system "#{reset} && #{reclean}"
42 | return ["fail", "Auto-update of Frigga Failed!"]
43 | end
44 |
45 | if restart_flag
46 | Thread.new { sleep 1; exit}
47 | return ["succ", "Auto-update of Frigga Succeed, ready to restart"]
48 | else
49 | return ["succ", "Frigga is already up to date, Don't need restart."]
50 | end
51 | end
52 |
53 | else
54 | return ["fail", "Don't have git and .git, won't auto-update!"]
55 | end
56 |
57 | end # update
58 |
59 | def update_god
60 | Dir.chdir Pathname.new(__FILE__).realpath + "../../../../script"
61 | `./run.rb stop_god`
62 | sleep(1)
63 | `./run.rb start`
64 | end
65 |
66 | end
67 | end
68 |
69 | end
70 |
--------------------------------------------------------------------------------
/views/index.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | | Process |
5 | Status |
6 | Start Time |
7 | Action |
8 |
9 |
11 | <% if @process.empty? %>
12 |
13 | | N/A |
14 | |
15 | |
16 | |
17 |
18 | <% end %>
19 | <% @process.each do |k, v| %>
20 | <% next if k =~ /Frigga/i %>
21 |
22 | |
23 | data-toggle="tooltip" data-placement="bottom" title="" data-original-title="<%= v[:start] %>"><%= k %>
24 | |
25 |
26 | <% if v[:status] == 'running' %>
27 | <%= v[:status] %> |
28 | <% else %>
29 | <%= v[:status] %>
30 | <% end %>
31 | <%= v[:start_time] %> |
32 |
33 | <% if v[:status] == 'stop' %>
34 |
38 | <% else %>
39 |
43 | <% end %>
44 |
45 |
49 |
50 |
69 |
70 | |
71 |
72 | <% end %>
73 |
74 |
75 |
76 |
83 |
--------------------------------------------------------------------------------
/views/ctmgr.erb:
--------------------------------------------------------------------------------
1 |
2 | @ctstatus
3 |
4 |
5 | | Process |
6 | Status |
7 | Start Time |
8 | Action |
9 |
10 |
12 | <% if @process.empty? %>
13 |
14 | | N/A |
15 | |
16 | |
17 | |
18 |
19 | <% end %>
20 | <% @process.each do |k, v| %>
21 | <% next if k =~ /Frigga/i %>
22 |
23 | |
24 | data-toggle="tooltip" data-placement="bottom" title="" data-original-title="<%= v[:start] %>"><%= k %>
25 | |
26 |
27 | <% if v[:status] == 'running' %>
28 | <%= v[:status] %> |
29 | <% else %>
30 | <%= v[:status] %>
31 | <% end %>
32 | <%= v[:start_time] %> |
33 |
34 | <% if v[:status] == 'stop' %>
35 |
39 | <% else %>
40 |
44 | <% end %>
45 |
46 |
50 |
51 |
70 |
71 | |
72 |
73 | <% end %>
74 |
75 |
76 |
77 |
84 |
--------------------------------------------------------------------------------
/views/script/run.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #ecoding: utf-8
3 | require "pathname"
4 |
5 | Dir.chdir Pathname.new(__FILE__).realpath + "../.."
6 |
7 | # Auto-update code
8 | if system("which git > /dev/null") && File.directory?(".git")
9 | clean = `git status --porcelain`.empty?
10 | current_branch = `git symbolic-ref HEAD`.chomp
11 | master = current_branch == "refs/heads/master"
12 | no_new_commits = system('git diff --exit-code --quiet origin/master master')
13 |
14 | short_branch = current_branch.split('/').last
15 |
16 | if !master
17 | warn "Frigga on a non-master branch '#{short_branch}', won't auto-update!"
18 | elsif !no_new_commits
19 | warn "Frigga has unpushed commits on master, won't auto-update!"
20 | elsif !clean
21 | warn "Frigga has a dirty tree, won't auto-update!"
22 | end
23 |
24 | if clean && master && no_new_commits
25 | quietly = "> /dev/null 2>&1"
26 | fetch = "(git fetch origin #{quietly})"
27 | reset = "(git reset --hard origin/master #{quietly})"
28 | reclean = "(git clean -df #{quietly})"
29 |
30 | unless system "#{fetch} && #{reset} && #{reclean}"
31 | warn "Auto-update of Frigga FAILED, continuing."
32 | end
33 | end
34 | else
35 | warn "Don't have git or .git, won't auto-update!"
36 | end
37 |
38 | # update gem
39 |
40 | gem_lock =Pathname.new(File.dirname(__FILE__)).realpath.to_s.split("/")[0..-2].join("/") + "/Gemfile.lock"
41 | `rm -rf #{gem_lock}`
42 | `bundle update --local`
43 | abort "Install gem failed!" if $? != 0
44 |
45 | DIR = File.expand_path("")
46 | abort "God does not exist..." unless system("which god > /dev/null")
47 |
48 | require "thor"
49 | class Cli < Thor
50 | desc "start", "Start God, Frigga and #{DIR}/gods/*.god"
51 | def start
52 | #wake up god
53 | wakeup_god = %W(god --no-events --log-level info -c #{DIR}/conf/base.god)
54 | abort "Start God failed!" unless system *wakeup_god
55 |
56 | #use god to load frigga.god for wakeing frigga up
57 | wakeup_frigga = "god load conf/frigga.god"
58 | abort "Start Frigga failed!" unless system *wakeup_frigga
59 |
60 | #start process
61 | Dir.glob(File.join(Dir.pwd, 'gods', "*.god")) do |god|
62 | start_process = "god load #{god}"
63 | `#{start_process}`
64 | warn "Start process[#{god}] failed!" unless $? == 0
65 | end
66 |
67 | #check process status
68 | puts "Command: god status"
69 | system("god status")
70 | end
71 |
72 | desc "stop", "Stop God and Frigga, Don't stop *.god"
73 | def stop
74 | #stop frigg
75 | stop_frigg = "god stop frigga"
76 | `#{stop_frigg}`
77 | warn "Stop Frigga failed!" unless $? == 0
78 |
79 | #stop god
80 | stop_god = %W(god quit)
81 | abort "Stop God failed!" unless system *stop_god
82 | end
83 |
84 | desc "nuke", "Stop God,Frigga and *.god"
85 | def nuke
86 | #terminate god
87 | nuke_god = %W(god terminate)
88 | abort "Nuke all gods failed!" unless system *nuke_god
89 | end
90 |
91 | desc "stop_god", "Stop God only, update god gem."
92 | def stop_god
93 | # update god
94 | stop_god = %W(god quit)
95 | abort "Stop God failed!" unless system *stop_god
96 | end
97 |
98 | end
99 |
100 | Cli.start(ARGV)
101 |
102 |
--------------------------------------------------------------------------------
/lib/frigga.rb:
--------------------------------------------------------------------------------
1 | $: << "./"
2 | require 'sinatra/base'
3 | require 'rack/rpc'
4 | require 'frigga/log'
5 | require 'frigga/auth'
6 | require 'frigga/talk'
7 | require 'frigga/rpc'
8 | require 'sinatra-websocket'
9 | require "file/tail"
10 |
11 | module Frigga
12 | class Tail < File
13 | include File::Tail
14 | end
15 |
16 | class WebServer < Sinatra::Base
17 | include Frigga::Auth
18 | configure do
19 | set :public_folder, Proc.new { File.join(File.expand_path(""), "static") }
20 | set :views, Proc.new { File.join(File.expand_path(""), "views") }
21 | enable :sessions
22 | set :port => Http_port
23 | set :bind => '0.0.0.0'
24 | set :show_exceptions => false
25 | end
26 |
27 | before '*' do
28 | Logger.info "[#{request.ip}] #{request.path}"
29 | unless Http_auth_user.nil?
30 | if White_list.include?(request.ip)
31 | Logger.info "[#{request.ip}] in white-list, skip Auth!"
32 | else
33 | protected! unless request.websocket?
34 | end
35 | end
36 | @ver = VER
37 | end
38 |
39 | not_found do
40 | erb :notfound
41 | end
42 |
43 | error do
44 | @error = env['sinatra.error'].message
45 | erb :error
46 | end
47 |
48 | get '/' do
49 | if session[:notice]
50 | @msg = session[:notice].dup
51 | session[:notice] = nil
52 | end
53 | @process = Frigga::Talk.god('status')
54 | erb :index
55 | end
56 |
57 | get '/about' do
58 | @process = Frigga::Talk.god('status')
59 | raise "Frigga hasn't watched by God" if @process.empty?
60 | erb :about
61 | end
62 |
63 | get '/log/*' do
64 | #check log file
65 | name, type, index = params[:splat][0].split('/')
66 | raise "url error" if name.nil? or type.nil?
67 | log = nil
68 | log_file = nil
69 | thr = nil
70 | if name == "god"
71 | if File.exist?("/var/log/messages")
72 | log_file = "/var/log/messages"
73 | elsif File.exist?("/var/log/syslog")
74 | log_file = "/var/log/syslog"
75 | end
76 | else
77 | process = Frigga::Talk.god('status')
78 | raise "Don't have name:#{name}" unless process.key?(name)
79 | if !process[name][:all_log][type.to_sym].nil? && !process[name][:all_log][type.to_sym].eql?("/dev/null")
80 | if process[name][:all_log][type.to_sym].kind_of?(Array)
81 | if !index.nil? && (0 ... process[name][:all_log][type.to_sym].size).include?(index.to_i)
82 | log_file = process[name][:all_log][type.to_sym][index.to_i] if File.exist?(process[name][:all_log][type.to_sym][index.to_i])
83 | end
84 | else
85 | log_file = process[name][:all_log][type.to_sym] if File.exist?(process[name][:all_log][type.to_sym])
86 | end
87 | end
88 | end
89 | raise "We can't find log file, sorry..." if log_file.nil?
90 |
91 | #start websocet
92 | if !request.websocket?
93 | erb :tail
94 | else
95 | request.websocket do |ws|
96 | ws.onopen do
97 | log = Tail.new(log_file)
98 | log.interval = 5
99 | log.backward(5)
100 | log.return_if_eof = true
101 | thr = Thread.new {
102 | loop do
103 | unless log.closed?
104 | log.tail { |line| ws.send line }
105 | end
106 | sleep 0.25
107 | end
108 | }
109 | end
110 |
111 | ws.onmessage do |msg|
112 | if msg =~ /END/i
113 | thr.kill if thr.alive?
114 | log.close unless log.closed?
115 | ws.close_connection
116 | end
117 | end
118 |
119 | ws.onclose do
120 | thr.kill if thr.alive?
121 | log.close unless log.closed?
122 | end
123 | end
124 | end
125 | end
126 |
127 | post '/god/:action' do |action|
128 | unless %w(restart start stop).include?(action)
129 | raise "Don't know action[#{action}]"
130 | end
131 | hi = Frigga::Talk.god(action, params[:name])
132 | if hi.empty?
133 | raise "#{action.capitalize} #{params[:name]} failed! #{hi[1]}"
134 | end
135 | session[:notice] = "Action: #{action} process[#{params[:name]}] success!"
136 | sleep 0.5
137 | redirect to '/'
138 | end
139 |
140 | use Rack::RPC::Endpoint, Frigga::RPC::Runner.new
141 | end
142 | end
143 |
144 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 介绍
2 |
3 | Frigga是一款使用简单、极具扩展的进程监控的框架。她基于开源的god,修改和添加了web界面和rpc接口,以满足大集群服务管理的需求。
4 |
5 | 在北欧神话中,frigga是神后,odin的妻子;掌管婚姻和家庭;负责纺织云彩
6 |
7 | 
8 |
9 | ## 功能
10 |
11 | - 集成了god,用来作为程序的supervise程序
12 | - C/S结构,并且集成了多种认证方式,以支持大集群运维管理
13 | - 基本功能均提供api接口,方便扩展
14 | - 支持单机web化的god,方便查看和管理
15 | - 支持日志查看
16 | - 支持添加自定义的xmlrpc接口,方便进行二次开发
17 |
18 |
19 | ## 环境依赖
20 |
21 | - Ruby 1.9.3
22 | - bundle
23 |
24 | ## 安装
25 |
26 | ```
27 | git clone git@github.com:xiaomi-sa/frigga.git
28 | ```
29 |
30 | ## 使用
31 |
32 | ### 基本用法
33 |
34 | 启动frigga god 以及需要启动的supervise程序
35 |
36 | ```
37 | cd script/ && ./run.rb start
38 | ```
39 | - 第一次使用会使用bundle安装vendor/cache/*.gem到系统
40 | - 在run.sh中,调用`god --no-events --log-level info -c #{DIR}/conf/base.god`启动god
41 | - 在run.sh中,通过god启动的frigga `god load conf/frigga.god`
42 |
43 | 通过浏览器链接http://localhost:9901, 默认用户名: admin, 默认密码: 123,可以在web查看
44 |
45 | ### 用god管理进程
46 | - [god官方地址](http://godrb.com)
47 | - [noops.me](http://noops.me/?p=133)上有god的使用介绍和其他进程管理工具的对比
48 |
49 | 查看启动的进程: `god status`
50 |
51 |
52 | ```
53 | god start/stop/restart process_name
54 | ```
55 |
56 | ### 使用god管理进程
57 |
58 | `添加god配置文件到 gods/ 目录下, 配置文件以.god作为后缀`,使用script/run.sh start时,会批量加载该目录下的*.god文件
59 |
60 | ### 建议的god配置
61 | ``` ruby
62 | God.watch do |w|
63 | w.name = "agent"
64 | w.start = "/home/work/opdir/agent/release/run.sh"
65 | w.log = "/home/work/opdir/agent/release/run.log"
66 | w.process_log = "/home/work/opdir/agent/log/agent.log"
67 | w.dir = "/home/work/opdir/agent/release"
68 | w.env = {"PATH"=>"/home/work/jdk/bin/:/bin:/usr/bin:/sbin", "JAVA_HOME"=>"/home/work/jdk", "CLASSPATH"=>".:/home/work/jdk/lib/tools.jar:/home/work/jdk/lib/rt.jar"}
69 | w.keepalive
70 | w.behavior(:clean_pid_file)
71 | w.stop_timeout = 60.seconds
72 |
73 | w.lifecycle do |on|
74 | on.condition(:flapping) do |c|
75 | c.to_state = [:start, :restart]
76 | c.times = 3
77 | c.within = 10.minute
78 | c.transition = :unmonitored
79 | c.retry_in = 20.minutes
80 | c.retry_times = 2
81 | c.retry_within = 1.hours
82 | c.notify = 'proc_down'
83 | end
84 | end
85 | end
86 |
87 | God.contact(:email) do |c|
88 | c.name = 'proc_down'
89 | c.group = 'developers'
90 |
91 | c.to_email = "god@xiaomi.com"
92 | end
93 | ```
94 |
95 | > 其中http_url, process_log为frigga支持的新参数
96 | > w.http_url = "www.xiaomi.com", 在web端点击process name,可以跳转到指定的url地址
97 |
98 | > w.process_log 支持数组或字符串配置
99 | >> w.process_log = "/home/work/xxx/log/xxx.log"
100 |
101 | >> w.process_log = ["/home/work/xxx/log/xxx.log", "/home/work/xxx/log/xxx1.log"]
102 |
103 | 配置好process_log后,可以通过god log process_name或web端查看日志, 如进程名为w.name = "frigga"
104 |
105 | `god log frigga [1/2/3...]`
106 |
107 | ### 为frigga添加白名单
108 |
109 | `在conf/下添加ip.yml文件,将需要信任的机器加入`
110 |
111 |
112 | ```
113 | ---
114 | - 10.237.37.23
115 | - 10.237.37.24
116 | ```
117 |
118 | ### 高级功能
119 |
120 |
121 | `添加自定义xmlrpc接口`
122 |
123 | > rpc接口的添加可以参照lib/frigga/rpc/god.rb
124 |
125 | > 假设我们要添加一个叫做call_somethg的接口,支持一个叫做do_somethg的方法,可以按照下面方法开发:
126 | >> 首先在lib/frigga/rpc/下添加一个call_somethg.rb的文件
127 |
128 | >> call_somethg.rb文件中的namespace应该遵循以下结构。需要注意的是要将需要调用的function写入到RPC_LIST数组中,作为方法的注册。
129 |
130 | ```
131 | module Frigga
132 | module RPC
133 | module call_somethg
134 |
135 | RPC_LIST = %w(do_somethg)
136 |
137 | def do_somethg
138 | do_somethg
139 | end
140 |
141 | end
142 | end
143 | end
144 |
145 | ```
146 |
147 | `使用rpc接口,遵循标准的xmlrpc协议.以下是一个调用demo`
148 |
149 | python的调用方法
150 | ``` python
151 | import xmlrpclib
152 | server_ip = "your_ip"
153 | server_port = "9001"
154 | uri = "http://" + server_ip + ":" + server_port + "/rpc"
155 | server = xmlrpclib.ServerProxy(uri)
156 | print server.call_somethg.do_somethg()
157 | ```
158 |
159 | ruby的调用方法
160 | ``` ruby
161 | require "xmlrpc/client"
162 | require "pp"
163 | server = XMLRPC::Client.new2("http://127.0.0.1:9001/rpc")
164 | puts server.call('help')
165 | puts "-----------------------"
166 | loop do
167 | begin
168 | x = gets.strip.split(/\s/)
169 | next if x.empty?
170 | result = server.call(*x)
171 | pp result
172 | puts "-----------------------"
173 | rescue => e
174 | puts e.message
175 | puts e.backtrace.join("\n")
176 | next
177 | end
178 | end
179 | ```
180 | 调用`help`方法,会列出frigga支持的所有rpc call
181 |
182 | ## 注意事项
183 |
184 | - 由于script/run.rb启动god时关闭了event,所以不能使用god event配置
185 |
186 | ## Help!
187 | 联系 xiedanbo <xiedanbo@xiaomi.com>
188 |
--------------------------------------------------------------------------------
/views/log/frigga.log:
--------------------------------------------------------------------------------
1 | # Logfile created on 2013-05-03 00:00:34 +0800 by logger.rb/31641
2 | 2013-05-03 00:00:34 +0800 INFO -- : [192.168.1.106] /ctmgr
3 | 2013-05-03 00:00:34 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
4 | 2013-05-03 00:00:34 +0800 INFO -- : [192.168.1.106] /favicon.ico
5 | 2013-05-03 00:00:34 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
6 | 2013-05-03 00:00:35 +0800 INFO -- : [192.168.1.106] /ctmgr
7 | 2013-05-03 00:00:35 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
8 | 2013-05-03 00:00:35 +0800 INFO -- : [192.168.1.106] /favicon.ico
9 | 2013-05-03 00:00:35 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
10 | 2013-05-03 00:00:37 +0800 INFO -- : [192.168.1.106] /ctmgr
11 | 2013-05-03 00:00:37 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
12 | 2013-05-03 00:00:37 +0800 INFO -- : [192.168.1.106] /favicon.ico
13 | 2013-05-03 00:00:37 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
14 | 2013-05-03 00:00:42 +0800 INFO -- : [192.168.1.106] /ctmgr
15 | 2013-05-03 00:00:42 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
16 | 2013-05-03 00:00:42 +0800 INFO -- : [192.168.1.106] /favicon.ico
17 | 2013-05-03 00:00:42 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
18 | 2013-05-03 00:00:45 +0800 INFO -- : [192.168.1.106] /ctmgr
19 | 2013-05-03 00:00:45 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
20 | 2013-05-03 00:00:46 +0800 INFO -- : [192.168.1.106] /favicon.ico
21 | 2013-05-03 00:00:46 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
22 | 2013-05-03 00:00:53 +0800 INFO -- : [192.168.1.106] /ctmgr
23 | 2013-05-03 00:00:53 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
24 | 2013-05-03 00:00:53 +0800 INFO -- : [192.168.1.106] /favicon.ico
25 | 2013-05-03 00:00:53 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
26 | 2013-05-03 00:01:44 +0800 INFO -- : [192.168.1.106] /ctmgr
27 | 2013-05-03 00:01:44 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
28 | 2013-05-03 00:01:45 +0800 INFO -- : [192.168.1.106] /favicon.ico
29 | 2013-05-03 00:01:45 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
30 | 2013-05-03 00:04:42 +0800 INFO -- : [192.168.1.106] /ctmgr
31 | 2013-05-03 00:04:42 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
32 | 2013-05-03 00:04:42 +0800 INFO -- : [192.168.1.106] /favicon.ico
33 | 2013-05-03 00:04:42 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
34 | 2013-05-03 00:04:44 +0800 INFO -- : [192.168.1.106] /ctmgr
35 | 2013-05-03 00:04:44 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
36 | 2013-05-03 00:04:44 +0800 INFO -- : [192.168.1.106] /favicon.ico
37 | 2013-05-03 00:04:44 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
38 | 2013-05-03 00:05:37 +0800 INFO -- : [192.168.1.106] /ctmgr
39 | 2013-05-03 00:05:37 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
40 | 2013-05-03 00:05:37 +0800 INFO -- : [192.168.1.106] /favicon.ico
41 | 2013-05-03 00:05:37 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
42 | 2013-05-03 00:05:41 +0800 INFO -- : [192.168.1.106] /ctmgr
43 | 2013-05-03 00:05:41 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
44 | 2013-05-03 00:05:41 +0800 INFO -- : [192.168.1.106] /favicon.ico
45 | 2013-05-03 00:05:41 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
46 | 2013-05-03 00:05:44 +0800 INFO -- : [192.168.1.106] /ctmgr
47 | 2013-05-03 00:05:44 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
48 | 2013-05-03 00:05:44 +0800 INFO -- : [192.168.1.106] /favicon.ico
49 | 2013-05-03 00:05:44 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
50 | 2013-05-03 00:06:27 +0800 INFO -- : [192.168.1.106] /ctmgr
51 | 2013-05-03 00:06:27 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
52 | 2013-05-03 00:06:27 +0800 INFO -- : [192.168.1.106] /favicon.ico
53 | 2013-05-03 00:06:27 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
54 | 2013-05-03 00:07:07 +0800 INFO -- : [10.180.2.13] called ctmgr.getCtstatus
55 | 2013-05-03 00:08:45 +0800 INFO -- : [10.180.2.13] called ctmgr.getCtstatus
56 | 2013-05-03 00:18:55 +0800 INFO -- : [192.168.1.106] /ctmgr
57 | 2013-05-03 00:18:55 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
58 | 2013-05-03 00:18:56 +0800 INFO -- : [192.168.1.106] /favicon.ico
59 | 2013-05-03 00:18:56 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
60 | 2013-05-03 00:28:08 +0800 INFO -- : [192.168.1.106] /ctmgr
61 | 2013-05-03 00:28:08 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
62 | 2013-05-03 00:28:09 +0800 INFO -- : [192.168.1.106] /favicon.ico
63 | 2013-05-03 00:28:09 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
64 | 2013-05-03 00:28:10 +0800 INFO -- : [192.168.1.106] /ctmgr
65 | 2013-05-03 00:28:10 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
66 | 2013-05-03 00:28:10 +0800 INFO -- : [192.168.1.106] /favicon.ico
67 | 2013-05-03 00:28:10 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
68 |
--------------------------------------------------------------------------------
/views/log/frigga.log.20130501:
--------------------------------------------------------------------------------
1 | # Logfile created on 2013-05-01 18:05:45 +0800 by logger.rb/31641
2 | 2013-05-01 18:05:45 +0800 INFO -- : Init env. Mkdir /home/xiedanbo/frigga/deploy/log/
3 | 2013-05-01 18:05:45 +0800 INFO -- : Init env. Mkdir /home/xiedanbo/frigga/deploy/tmp/
4 | 2013-05-01 18:06:07 +0800 INFO -- : [10.180.2.13] called ctmgr.getCtstatus
5 | 2013-05-01 18:07:47 +0800 INFO -- : [192.168.1.106] /
6 | 2013-05-01 18:07:52 +0800 INFO -- : [192.168.1.106] /favicon.ico
7 | 2013-05-01 18:08:45 +0800 INFO -- : [192.168.1.106] /
8 | 2013-05-01 18:08:45 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
9 | 2013-05-01 18:08:45 +0800 INFO -- : [192.168.1.106] /favicon.ico
10 | 2013-05-01 18:08:45 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
11 | 2013-05-01 18:10:08 +0800 INFO -- : [192.168.1.106] /log/god/0
12 | 2013-05-01 18:10:08 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
13 | 2013-05-01 18:10:08 +0800 INFO -- : [192.168.1.106] /log/god/0
14 | 2013-05-01 18:10:08 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
15 | 2013-05-01 18:10:08 +0800 INFO -- : [192.168.1.106] /favicon.ico
16 | 2013-05-01 18:10:08 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
17 | 2013-05-01 18:10:09 +0800 INFO -- : [192.168.1.106] /
18 | 2013-05-01 18:10:09 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
19 | 2013-05-01 18:10:09 +0800 INFO -- : [192.168.1.106] /favicon.ico
20 | 2013-05-01 18:10:09 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
21 | 2013-05-01 18:10:10 +0800 INFO -- : [192.168.1.106] /about
22 | 2013-05-01 18:10:10 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
23 | 2013-05-01 18:10:10 +0800 INFO -- : [192.168.1.106] /favicon.ico
24 | 2013-05-01 18:10:10 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
25 | 2013-05-01 18:10:11 +0800 INFO -- : [192.168.1.106] /log/god/0
26 | 2013-05-01 18:10:11 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
27 | 2013-05-01 18:10:12 +0800 INFO -- : [192.168.1.106] /favicon.ico
28 | 2013-05-01 18:10:12 +0800 INFO -- : [192.168.1.106] /log/god/0
29 | 2013-05-01 18:10:12 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
30 | 2013-05-01 18:10:12 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
31 | 2013-05-01 18:10:13 +0800 INFO -- : [192.168.1.106] /
32 | 2013-05-01 18:10:13 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
33 | 2013-05-01 18:10:13 +0800 INFO -- : [192.168.1.106] /favicon.ico
34 | 2013-05-01 18:10:13 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
35 | 2013-05-01 18:11:30 +0800 INFO -- : [192.168.1.106] /log/god/0
36 | 2013-05-01 18:11:30 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
37 | 2013-05-01 18:11:30 +0800 INFO -- : [192.168.1.106] /log/god/0
38 | 2013-05-01 18:11:30 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
39 | 2013-05-01 18:11:30 +0800 INFO -- : [192.168.1.106] /favicon.ico
40 | 2013-05-01 18:11:30 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
41 | 2013-05-01 18:15:48 +0800 INFO -- : [192.168.1.106] /
42 | 2013-05-01 18:15:48 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
43 | 2013-05-01 18:15:48 +0800 INFO -- : [192.168.1.106] /favicon.ico
44 | 2013-05-01 18:15:48 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
45 | 2013-05-01 18:16:04 +0800 INFO -- : [192.168.1.106] /about
46 | 2013-05-01 18:16:04 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
47 | 2013-05-01 18:16:04 +0800 INFO -- : [192.168.1.106] /favicon.ico
48 | 2013-05-01 18:16:04 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
49 | 2013-05-01 18:16:08 +0800 INFO -- : [192.168.1.106] /ctmgr
50 | 2013-05-01 18:16:08 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
51 | 2013-05-01 18:16:09 +0800 INFO -- : [192.168.1.106] /favicon.ico
52 | 2013-05-01 18:16:09 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
53 | 2013-05-01 18:17:57 +0800 INFO -- : [192.168.1.106] /ctmgr
54 | 2013-05-01 18:17:57 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
55 | 2013-05-01 18:17:57 +0800 INFO -- : [192.168.1.106] /favicon.ico
56 | 2013-05-01 18:17:57 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
57 | 2013-05-01 18:17:58 +0800 INFO -- : [192.168.1.106] /ctmgr
58 | 2013-05-01 18:17:58 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
59 | 2013-05-01 18:17:58 +0800 INFO -- : [192.168.1.106] /favicon.ico
60 | 2013-05-01 18:17:58 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
61 | 2013-05-01 18:17:59 +0800 INFO -- : [192.168.1.106] /ctmgr
62 | 2013-05-01 18:17:59 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
63 | 2013-05-01 18:17:59 +0800 INFO -- : [192.168.1.106] /favicon.ico
64 | 2013-05-01 18:17:59 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
65 | 2013-05-01 18:18:21 +0800 INFO -- : [192.168.1.106] /ctmgr
66 | 2013-05-01 18:18:21 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
67 | 2013-05-01 18:18:21 +0800 INFO -- : [192.168.1.106] /favicon.ico
68 | 2013-05-01 18:18:21 +0800 INFO -- : [192.168.1.106] in white-list, skip Auth!
69 |
--------------------------------------------------------------------------------
/views/lib/frigga.rb:
--------------------------------------------------------------------------------
1 | $: << "./"
2 | require 'sinatra/base'
3 | require 'rack/rpc'
4 | require 'frigga/log'
5 | require 'frigga/auth'
6 | require 'frigga/talk'
7 | require 'frigga/rpc'
8 | require 'sinatra-websocket'
9 | require "file/tail"
10 |
11 | module Frigga
12 | class Tail < File
13 | include File::Tail
14 | end
15 |
16 | class WebServer < Sinatra::Base
17 | include Frigga::Auth
18 | configure do
19 | set :public_folder, Proc.new { File.join(File.expand_path(""), "static") }
20 | set :views, Proc.new { File.join(File.expand_path(""), "views") }
21 | enable :sessions
22 | set :port => Http_port
23 | set :bind => '0.0.0.0'
24 | set :show_exceptions => false
25 | end
26 | # use Rack::Auth::Basic, "Protected Area" do |username, password|
27 | # username == 'foo' && password == 'bar'
28 | # end
29 | before '*' do
30 | Logger.info "[#{request.ip}] #{request.path}"
31 | if White_list.include?(request.ip)
32 | Logger.info "[#{request.ip}] in white-list, skip Auth!"
33 | else
34 | protected! unless request.websocket?
35 | end
36 | @ver = VER
37 | end
38 |
39 | not_found do
40 | erb :notfound
41 | end
42 |
43 | error do
44 | @error = env['sinatra.error'].message
45 | erb :error
46 | end
47 |
48 | get '/' do
49 | if session[:notice]
50 | @msg = session[:notice].dup
51 | session[:notice] = nil
52 | end
53 | @process = Frigga::Talk.god('status')
54 | erb :index
55 | end
56 |
57 | get '/about' do
58 | @process = Frigga::Talk.god('status')
59 | raise "Frigga hasn't watched by God" if @process.empty?
60 | erb :about
61 | end
62 |
63 | get '/log/*' do
64 | #check log file
65 | name, type, index = params[:splat][0].split('/')
66 | raise "url error" if name.nil? or type.nil?
67 | log = nil
68 | log_file = nil
69 | thr = nil
70 | if name == "god"
71 | if File.exist?("/var/log/messages")
72 | log_file = "/var/log/messages"
73 | elsif File.exist?("/var/log/syslog")
74 | log_file = "/var/log/syslog"
75 | end
76 | else
77 | process = Frigga::Talk.god('status')
78 | raise "Don't have name:#{name}" unless process.key?(name)
79 | if !process[name][:all_log][type.to_sym].nil? && !process[name][:all_log][type.to_sym].eql?("/dev/null")
80 | if process[name][:all_log][type.to_sym].kind_of?(Array)
81 | if !index.nil? && (0 .. process[name][:all_log][type.to_sym].size-1).include?(index.to_i)
82 | log_file = process[name][:all_log][type.to_sym][index.to_i] if File.exist?(process[name][:all_log][type.to_sym][index.to_i])
83 | end
84 | else
85 | log_file = process[name][:all_log][type.to_sym] if File.exist?(process[name][:all_log][type.to_sym])
86 | end
87 | end
88 | end
89 | raise "We can't find log file, sorry..." if log_file.nil?
90 |
91 | #start websocet
92 | if !request.websocket?
93 | erb :tail
94 | else
95 | request.websocket do |ws|
96 | ws.onopen do
97 | log = Tail.new(log_file)
98 | log.interval = 5
99 | log.backward(5)
100 | log.return_if_eof = true
101 | thr = Thread.new {
102 | loop do
103 | unless log.closed?
104 | log.tail { |line| ws.send line }
105 | end
106 | sleep 0.25
107 | end
108 | }
109 | end
110 |
111 | ws.onmessage do |msg|
112 | if msg =~ /END/i
113 | thr.kill if thr.alive?
114 | log.close unless log.closed?
115 | ws.close_connection
116 | end
117 | end
118 |
119 | ws.onclose do
120 | thr.kill if thr.alive?
121 | log.close unless log.closed?
122 | end
123 | end
124 | end
125 | end
126 |
127 | post '/god/:action' do |action|
128 | unless %w(restart start stop).include?(action)
129 | raise "Don't know action[#{action}]"
130 | end
131 | hi = Frigga::Talk.god(action, params[:name])
132 | if hi.empty?
133 | raise "#{action.capitalize} #{params[:name]} failed! #{hi[1]}"
134 | end
135 | session[:notice] = "Action: #{action} process[#{params[:name]}] success!"
136 | sleep 0.5
137 | redirect to '/'
138 | end
139 |
140 | #get '/ctmgr' do
141 | # if session[:notice]
142 | # @msg = session[:notice].dup
143 | # session[:notice] = nil
144 | # end
145 | # include Frigga::RPC::Ctmgr
146 | # @ctstatus = getCtstatus()
147 | # erb :ctmgr
148 | #end
149 |
150 | use Rack::RPC::Endpoint, Frigga::RPC::Runner.new
151 | end
152 | end
153 |
154 |
--------------------------------------------------------------------------------
/static/vendor/bootstrap/js/bootstrap-tooltip.js:
--------------------------------------------------------------------------------
1 | /* ===========================================================
2 | * bootstrap-tooltip.js v2.3.1
3 | * http://twitter.github.com/bootstrap/javascript.html#tooltips
4 | * Inspired by the original jQuery.tipsy by Jason Frame
5 | * ===========================================================
6 | * Copyright 2012 Twitter, Inc.
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | * ========================================================== */
20 |
21 |
22 | !function ($) {
23 |
24 | "use strict"; // jshint ;_;
25 |
26 |
27 | /* TOOLTIP PUBLIC CLASS DEFINITION
28 | * =============================== */
29 |
30 | var Tooltip = function (element, options) {
31 | this.init('tooltip', element, options)
32 | }
33 |
34 | Tooltip.prototype = {
35 |
36 | constructor: Tooltip
37 |
38 | , init: function (type, element, options) {
39 | var eventIn
40 | , eventOut
41 | , triggers
42 | , trigger
43 | , i
44 |
45 | this.type = type
46 | this.$element = $(element)
47 | this.options = this.getOptions(options)
48 | this.enabled = true
49 |
50 | triggers = this.options.trigger.split(' ')
51 |
52 | for (i = triggers.length; i--;) {
53 | trigger = triggers[i]
54 | if (trigger == 'click') {
55 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
56 | } else if (trigger != 'manual') {
57 | eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
58 | eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
59 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
60 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
61 | }
62 | }
63 |
64 | this.options.selector ?
65 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
66 | this.fixTitle()
67 | }
68 |
69 | , getOptions: function (options) {
70 | options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
71 |
72 | if (options.delay && typeof options.delay == 'number') {
73 | options.delay = {
74 | show: options.delay
75 | , hide: options.delay
76 | }
77 | }
78 |
79 | return options
80 | }
81 |
82 | , enter: function (e) {
83 | var defaults = $.fn[this.type].defaults
84 | , options = {}
85 | , self
86 |
87 | this._options && $.each(this._options, function (key, value) {
88 | if (defaults[key] != value) options[key] = value
89 | }, this)
90 |
91 | self = $(e.currentTarget)[this.type](options).data(this.type)
92 |
93 | if (!self.options.delay || !self.options.delay.show) return self.show()
94 |
95 | clearTimeout(this.timeout)
96 | self.hoverState = 'in'
97 | this.timeout = setTimeout(function() {
98 | if (self.hoverState == 'in') self.show()
99 | }, self.options.delay.show)
100 | }
101 |
102 | , leave: function (e) {
103 | var self = $(e.currentTarget)[this.type](this._options).data(this.type)
104 |
105 | if (this.timeout) clearTimeout(this.timeout)
106 | if (!self.options.delay || !self.options.delay.hide) return self.hide()
107 |
108 | self.hoverState = 'out'
109 | this.timeout = setTimeout(function() {
110 | if (self.hoverState == 'out') self.hide()
111 | }, self.options.delay.hide)
112 | }
113 |
114 | , show: function () {
115 | var $tip
116 | , pos
117 | , actualWidth
118 | , actualHeight
119 | , placement
120 | , tp
121 | , e = $.Event('show')
122 |
123 | if (this.hasContent() && this.enabled) {
124 | this.$element.trigger(e)
125 | if (e.isDefaultPrevented()) return
126 | $tip = this.tip()
127 | this.setContent()
128 |
129 | if (this.options.animation) {
130 | $tip.addClass('fade')
131 | }
132 |
133 | placement = typeof this.options.placement == 'function' ?
134 | this.options.placement.call(this, $tip[0], this.$element[0]) :
135 | this.options.placement
136 |
137 | $tip
138 | .detach()
139 | .css({ top: 0, left: 0, display: 'block' })
140 |
141 | this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
142 |
143 | pos = this.getPosition()
144 |
145 | actualWidth = $tip[0].offsetWidth
146 | actualHeight = $tip[0].offsetHeight
147 |
148 | switch (placement) {
149 | case 'bottom':
150 | tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
151 | break
152 | case 'top':
153 | tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
154 | break
155 | case 'left':
156 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
157 | break
158 | case 'right':
159 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
160 | break
161 | }
162 |
163 | this.applyPlacement(tp, placement)
164 | this.$element.trigger('shown')
165 | }
166 | }
167 |
168 | , applyPlacement: function(offset, placement){
169 | var $tip = this.tip()
170 | , width = $tip[0].offsetWidth
171 | , height = $tip[0].offsetHeight
172 | , actualWidth
173 | , actualHeight
174 | , delta
175 | , replace
176 |
177 | $tip
178 | .offset(offset)
179 | .addClass(placement)
180 | .addClass('in')
181 |
182 | actualWidth = $tip[0].offsetWidth
183 | actualHeight = $tip[0].offsetHeight
184 |
185 | if (placement == 'top' && actualHeight != height) {
186 | offset.top = offset.top + height - actualHeight
187 | replace = true
188 | }
189 |
190 | if (placement == 'bottom' || placement == 'top') {
191 | delta = 0
192 |
193 | if (offset.left < 0){
194 | delta = offset.left * -2
195 | offset.left = 0
196 | $tip.offset(offset)
197 | actualWidth = $tip[0].offsetWidth
198 | actualHeight = $tip[0].offsetHeight
199 | }
200 |
201 | this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
202 | } else {
203 | this.replaceArrow(actualHeight - height, actualHeight, 'top')
204 | }
205 |
206 | if (replace) $tip.offset(offset)
207 | }
208 |
209 | , replaceArrow: function(delta, dimension, position){
210 | this
211 | .arrow()
212 | .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
213 | }
214 |
215 | , setContent: function () {
216 | var $tip = this.tip()
217 | , title = this.getTitle()
218 |
219 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
220 | $tip.removeClass('fade in top bottom left right')
221 | }
222 |
223 | , hide: function () {
224 | var that = this
225 | , $tip = this.tip()
226 | , e = $.Event('hide')
227 |
228 | this.$element.trigger(e)
229 | if (e.isDefaultPrevented()) return
230 |
231 | $tip.removeClass('in')
232 |
233 | function removeWithAnimation() {
234 | var timeout = setTimeout(function () {
235 | $tip.off($.support.transition.end).detach()
236 | }, 500)
237 |
238 | $tip.one($.support.transition.end, function () {
239 | clearTimeout(timeout)
240 | $tip.detach()
241 | })
242 | }
243 |
244 | $.support.transition && this.$tip.hasClass('fade') ?
245 | removeWithAnimation() :
246 | $tip.detach()
247 |
248 | this.$element.trigger('hidden')
249 |
250 | return this
251 | }
252 |
253 | , fixTitle: function () {
254 | var $e = this.$element
255 | if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
256 | $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
257 | }
258 | }
259 |
260 | , hasContent: function () {
261 | return this.getTitle()
262 | }
263 |
264 | , getPosition: function () {
265 | var el = this.$element[0]
266 | return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
267 | width: el.offsetWidth
268 | , height: el.offsetHeight
269 | }, this.$element.offset())
270 | }
271 |
272 | , getTitle: function () {
273 | var title
274 | , $e = this.$element
275 | , o = this.options
276 |
277 | title = $e.attr('data-original-title')
278 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
279 |
280 | return title
281 | }
282 |
283 | , tip: function () {
284 | return this.$tip = this.$tip || $(this.options.template)
285 | }
286 |
287 | , arrow: function(){
288 | return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
289 | }
290 |
291 | , validate: function () {
292 | if (!this.$element[0].parentNode) {
293 | this.hide()
294 | this.$element = null
295 | this.options = null
296 | }
297 | }
298 |
299 | , enable: function () {
300 | this.enabled = true
301 | }
302 |
303 | , disable: function () {
304 | this.enabled = false
305 | }
306 |
307 | , toggleEnabled: function () {
308 | this.enabled = !this.enabled
309 | }
310 |
311 | , toggle: function (e) {
312 | var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
313 | self.tip().hasClass('in') ? self.hide() : self.show()
314 | }
315 |
316 | , destroy: function () {
317 | this.hide().$element.off('.' + this.type).removeData(this.type)
318 | }
319 |
320 | }
321 |
322 |
323 | /* TOOLTIP PLUGIN DEFINITION
324 | * ========================= */
325 |
326 | var old = $.fn.tooltip
327 |
328 | $.fn.tooltip = function ( option ) {
329 | return this.each(function () {
330 | var $this = $(this)
331 | , data = $this.data('tooltip')
332 | , options = typeof option == 'object' && option
333 | if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
334 | if (typeof option == 'string') data[option]()
335 | })
336 | }
337 |
338 | $.fn.tooltip.Constructor = Tooltip
339 |
340 | $.fn.tooltip.defaults = {
341 | animation: true
342 | , placement: 'top'
343 | , selector: false
344 | , template: ''
345 | , trigger: 'hover focus'
346 | , title: ''
347 | , delay: 0
348 | , html: false
349 | , container: false
350 | }
351 |
352 |
353 | /* TOOLTIP NO CONFLICT
354 | * =================== */
355 |
356 | $.fn.tooltip.noConflict = function () {
357 | $.fn.tooltip = old
358 | return this
359 | }
360 |
361 | }(window.jQuery);
362 |
--------------------------------------------------------------------------------
/views/static/vendor/bootstrap/js/bootstrap-tooltip.js:
--------------------------------------------------------------------------------
1 | /* ===========================================================
2 | * bootstrap-tooltip.js v2.3.1
3 | * http://twitter.github.com/bootstrap/javascript.html#tooltips
4 | * Inspired by the original jQuery.tipsy by Jason Frame
5 | * ===========================================================
6 | * Copyright 2012 Twitter, Inc.
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | * ========================================================== */
20 |
21 |
22 | !function ($) {
23 |
24 | "use strict"; // jshint ;_;
25 |
26 |
27 | /* TOOLTIP PUBLIC CLASS DEFINITION
28 | * =============================== */
29 |
30 | var Tooltip = function (element, options) {
31 | this.init('tooltip', element, options)
32 | }
33 |
34 | Tooltip.prototype = {
35 |
36 | constructor: Tooltip
37 |
38 | , init: function (type, element, options) {
39 | var eventIn
40 | , eventOut
41 | , triggers
42 | , trigger
43 | , i
44 |
45 | this.type = type
46 | this.$element = $(element)
47 | this.options = this.getOptions(options)
48 | this.enabled = true
49 |
50 | triggers = this.options.trigger.split(' ')
51 |
52 | for (i = triggers.length; i--;) {
53 | trigger = triggers[i]
54 | if (trigger == 'click') {
55 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
56 | } else if (trigger != 'manual') {
57 | eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
58 | eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
59 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
60 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
61 | }
62 | }
63 |
64 | this.options.selector ?
65 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
66 | this.fixTitle()
67 | }
68 |
69 | , getOptions: function (options) {
70 | options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
71 |
72 | if (options.delay && typeof options.delay == 'number') {
73 | options.delay = {
74 | show: options.delay
75 | , hide: options.delay
76 | }
77 | }
78 |
79 | return options
80 | }
81 |
82 | , enter: function (e) {
83 | var defaults = $.fn[this.type].defaults
84 | , options = {}
85 | , self
86 |
87 | this._options && $.each(this._options, function (key, value) {
88 | if (defaults[key] != value) options[key] = value
89 | }, this)
90 |
91 | self = $(e.currentTarget)[this.type](options).data(this.type)
92 |
93 | if (!self.options.delay || !self.options.delay.show) return self.show()
94 |
95 | clearTimeout(this.timeout)
96 | self.hoverState = 'in'
97 | this.timeout = setTimeout(function() {
98 | if (self.hoverState == 'in') self.show()
99 | }, self.options.delay.show)
100 | }
101 |
102 | , leave: function (e) {
103 | var self = $(e.currentTarget)[this.type](this._options).data(this.type)
104 |
105 | if (this.timeout) clearTimeout(this.timeout)
106 | if (!self.options.delay || !self.options.delay.hide) return self.hide()
107 |
108 | self.hoverState = 'out'
109 | this.timeout = setTimeout(function() {
110 | if (self.hoverState == 'out') self.hide()
111 | }, self.options.delay.hide)
112 | }
113 |
114 | , show: function () {
115 | var $tip
116 | , pos
117 | , actualWidth
118 | , actualHeight
119 | , placement
120 | , tp
121 | , e = $.Event('show')
122 |
123 | if (this.hasContent() && this.enabled) {
124 | this.$element.trigger(e)
125 | if (e.isDefaultPrevented()) return
126 | $tip = this.tip()
127 | this.setContent()
128 |
129 | if (this.options.animation) {
130 | $tip.addClass('fade')
131 | }
132 |
133 | placement = typeof this.options.placement == 'function' ?
134 | this.options.placement.call(this, $tip[0], this.$element[0]) :
135 | this.options.placement
136 |
137 | $tip
138 | .detach()
139 | .css({ top: 0, left: 0, display: 'block' })
140 |
141 | this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
142 |
143 | pos = this.getPosition()
144 |
145 | actualWidth = $tip[0].offsetWidth
146 | actualHeight = $tip[0].offsetHeight
147 |
148 | switch (placement) {
149 | case 'bottom':
150 | tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
151 | break
152 | case 'top':
153 | tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
154 | break
155 | case 'left':
156 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
157 | break
158 | case 'right':
159 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
160 | break
161 | }
162 |
163 | this.applyPlacement(tp, placement)
164 | this.$element.trigger('shown')
165 | }
166 | }
167 |
168 | , applyPlacement: function(offset, placement){
169 | var $tip = this.tip()
170 | , width = $tip[0].offsetWidth
171 | , height = $tip[0].offsetHeight
172 | , actualWidth
173 | , actualHeight
174 | , delta
175 | , replace
176 |
177 | $tip
178 | .offset(offset)
179 | .addClass(placement)
180 | .addClass('in')
181 |
182 | actualWidth = $tip[0].offsetWidth
183 | actualHeight = $tip[0].offsetHeight
184 |
185 | if (placement == 'top' && actualHeight != height) {
186 | offset.top = offset.top + height - actualHeight
187 | replace = true
188 | }
189 |
190 | if (placement == 'bottom' || placement == 'top') {
191 | delta = 0
192 |
193 | if (offset.left < 0){
194 | delta = offset.left * -2
195 | offset.left = 0
196 | $tip.offset(offset)
197 | actualWidth = $tip[0].offsetWidth
198 | actualHeight = $tip[0].offsetHeight
199 | }
200 |
201 | this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
202 | } else {
203 | this.replaceArrow(actualHeight - height, actualHeight, 'top')
204 | }
205 |
206 | if (replace) $tip.offset(offset)
207 | }
208 |
209 | , replaceArrow: function(delta, dimension, position){
210 | this
211 | .arrow()
212 | .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
213 | }
214 |
215 | , setContent: function () {
216 | var $tip = this.tip()
217 | , title = this.getTitle()
218 |
219 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
220 | $tip.removeClass('fade in top bottom left right')
221 | }
222 |
223 | , hide: function () {
224 | var that = this
225 | , $tip = this.tip()
226 | , e = $.Event('hide')
227 |
228 | this.$element.trigger(e)
229 | if (e.isDefaultPrevented()) return
230 |
231 | $tip.removeClass('in')
232 |
233 | function removeWithAnimation() {
234 | var timeout = setTimeout(function () {
235 | $tip.off($.support.transition.end).detach()
236 | }, 500)
237 |
238 | $tip.one($.support.transition.end, function () {
239 | clearTimeout(timeout)
240 | $tip.detach()
241 | })
242 | }
243 |
244 | $.support.transition && this.$tip.hasClass('fade') ?
245 | removeWithAnimation() :
246 | $tip.detach()
247 |
248 | this.$element.trigger('hidden')
249 |
250 | return this
251 | }
252 |
253 | , fixTitle: function () {
254 | var $e = this.$element
255 | if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
256 | $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
257 | }
258 | }
259 |
260 | , hasContent: function () {
261 | return this.getTitle()
262 | }
263 |
264 | , getPosition: function () {
265 | var el = this.$element[0]
266 | return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
267 | width: el.offsetWidth
268 | , height: el.offsetHeight
269 | }, this.$element.offset())
270 | }
271 |
272 | , getTitle: function () {
273 | var title
274 | , $e = this.$element
275 | , o = this.options
276 |
277 | title = $e.attr('data-original-title')
278 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
279 |
280 | return title
281 | }
282 |
283 | , tip: function () {
284 | return this.$tip = this.$tip || $(this.options.template)
285 | }
286 |
287 | , arrow: function(){
288 | return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
289 | }
290 |
291 | , validate: function () {
292 | if (!this.$element[0].parentNode) {
293 | this.hide()
294 | this.$element = null
295 | this.options = null
296 | }
297 | }
298 |
299 | , enable: function () {
300 | this.enabled = true
301 | }
302 |
303 | , disable: function () {
304 | this.enabled = false
305 | }
306 |
307 | , toggleEnabled: function () {
308 | this.enabled = !this.enabled
309 | }
310 |
311 | , toggle: function (e) {
312 | var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
313 | self.tip().hasClass('in') ? self.hide() : self.show()
314 | }
315 |
316 | , destroy: function () {
317 | this.hide().$element.off('.' + this.type).removeData(this.type)
318 | }
319 |
320 | }
321 |
322 |
323 | /* TOOLTIP PLUGIN DEFINITION
324 | * ========================= */
325 |
326 | var old = $.fn.tooltip
327 |
328 | $.fn.tooltip = function ( option ) {
329 | return this.each(function () {
330 | var $this = $(this)
331 | , data = $this.data('tooltip')
332 | , options = typeof option == 'object' && option
333 | if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
334 | if (typeof option == 'string') data[option]()
335 | })
336 | }
337 |
338 | $.fn.tooltip.Constructor = Tooltip
339 |
340 | $.fn.tooltip.defaults = {
341 | animation: true
342 | , placement: 'top'
343 | , selector: false
344 | , template: ''
345 | , trigger: 'hover focus'
346 | , title: ''
347 | , delay: 0
348 | , html: false
349 | , container: false
350 | }
351 |
352 |
353 | /* TOOLTIP NO CONFLICT
354 | * =================== */
355 |
356 | $.fn.tooltip.noConflict = function () {
357 | $.fn.tooltip = old
358 | return this
359 | }
360 |
361 | }(window.jQuery);
362 |
--------------------------------------------------------------------------------
/static/vendor/bootstrap/css/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.3.0
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/views/static/vendor/bootstrap/css/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.3.0
3 | *
4 | * Copyright 2012 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/views/lib/frigga/rpc/deploy.rb:
--------------------------------------------------------------------------------
1 | module Frigga
2 |
3 | module RPC
4 |
5 | require "digest/md5"
6 | require "fileutils"
7 | require "pathname"
8 |
9 | BASE_PATH = Pathname.new(File.dirname(__FILE__)).realpath.to_s.split("/")[0..-4].join("/") + "/deploy/"
10 | DATA_PATH = BASE_PATH + "data/"
11 | TMP_PATH = BASE_PATH + "tmp/"
12 | LOG_PATH = BASE_PATH + "log/"
13 | HM_PATH = "/home/xbox/aesir/thor/thor/bin/thor.py"
14 | GOD_PATH = "/home/xbox/aesir/frigga/gods/"
15 | DEPLOY_LCK= LOG_PATH + "deploy.lck"
16 | LOG_SYMBOL= "="
17 |
18 | if not File.directory?LOG_PATH
19 | Logger.info "Init env. Mkdir #{LOG_PATH}"
20 | `mkdir -p #{LOG_PATH}`
21 | end
22 |
23 | if not File.directory?TMP_PATH
24 | Logger.info "Init env. Mkdir #{TMP_PATH}"
25 | `mkdir -p #{TMP_PATH}`
26 | end
27 |
28 | module Deploy
29 |
30 | RPC_LIST = %w( startDeploy getDeployStatus getDeployTaskStatus getDeployLog4Mod getDeployLog )
31 |
32 | # Hostname`s hash
33 | def _mk_id_part_hostname
34 | hostname = `hostname`
35 | checksum = Digest::MD5.hexdigest(hostname)
36 | return checksum[0,8]
37 | end
38 |
39 | # Create random string
40 | def _random_str(len=8)
41 | chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
42 | rand_str = ""
43 | 1.upto(len) { |i| rand_str << chars[rand(chars.size-1)] }
44 | return rand_str
45 | end
46 |
47 | # Download file ,return the abs path.
48 | def _down_file(path, url, file_name)
49 |
50 | down_cmd = "cd %s && wget %s -q -O ./%s" %[path, url, file_name]
51 | r = system(down_cmd)
52 | file_path = path + "/" + file_name
53 |
54 | if FileTest::exists?(file_path) and not FileTest::zero?(file_path)
55 | Logger.info "Download succ . file_path : #{file_path} url : #{url}"
56 | return ['task_id', 'succ', file_path]
57 | else
58 | Logger.fatal "Download fail. file_path : #{file_path} url : #{url}"
59 | return ['task_id', 'fail', file_path]
60 | end
61 | end
62 |
63 | # Unzip file.
64 | def _do_unzip(gz_file)
65 |
66 | # The zip_file like
67 | # "/home/xiedanbo/frigg/lib/deploy/tmp/eca12e6anPDHQkRE1364449531/cluster.tar.gz"
68 | # The filename_before like "cluster.tar.gz"
69 | # The filename_bef_tar like "cluster"
70 | # The task_id like "eca12e6anPDHQkRE1364449531"
71 | # The file_path like "/home/xiedanbo/frigg/lib/deploy/tmp/eca12e6anPDHQkRE1364449531"
72 |
73 | task_id, filename_before = gz_file.split("/")[-2..-1]
74 | filename_bef_tar = filename_before.split(".tar.gz")[0]
75 | file_path = gz_file.split("/")[0..-2].join("/")
76 | file_path_af_unzip = file_path + "/" + filename_bef_tar
77 |
78 | tar_cmd = "cd %s && tar zxf %s" %[file_path, filename_before]
79 | r = system(tar_cmd)
80 |
81 | if FileTest::exists?(file_path_af_unzip)
82 | Logger.info "Unzip #{gz_file} succ . file_path_af_unzip : #{file_path_af_unzip}"
83 | return [task_id, "succ", file_path_af_unzip]
84 | else
85 | Logger.fatal "Unzip #{gz_file} fail. file_path_af_unzip : #{file_path_af_unzip}"
86 | return [task_id, 'fail', 'Unzip the data failed.']
87 | end
88 |
89 | end
90 |
91 | # Through the shell execute the thor script.
92 | def _execute_thor(thor_data_path)
93 |
94 | task_id = thor_data_path.split("/")[-2]
95 | thor_log_path = LOG_PATH + "thor_" + task_id + ".log"
96 | deploy_cmd = "nohup /usr/bin/python -u %s -p %s -s %s &>%s &" \
97 | %[HM_PATH, thor_data_path, GOD_PATH, thor_log_path]
98 | r = system(deploy_cmd)
99 |
100 | Logger.info "Execute the thor succ. deploy_cmd >>> #{deploy_cmd} <<<"
101 | return [task_id, 'succ', 'hahaha']
102 |
103 | end
104 |
105 | # Do deploy task.
106 | def _do_deploy( task_id, port, path, host_ip)
107 |
108 | if path.start_with?("/")
109 | url = "http://%s:%s%s" %[host_ip.to_s, port.to_s, path.to_s]
110 | else
111 | url = "http://%s:%s/%s" %[host_ip.to_s, port.to_s, path.to_s]
112 | end
113 |
114 | tmp_file_path = TMP_PATH + task_id
115 | Logger.info "Task_id : #{task_id} url : #{url} tmp_file_path : #{tmp_file_path}"
116 |
117 | if FileTest::exists?(tmp_file_path)
118 | Logger.fatal "The tmp path for task was exists. #{tmp_file_path}"
119 | return [task_id, "fail","The tmp path for task was exists."]
120 | end
121 |
122 | begin
123 | FileUtils.mkdir(tmp_file_path)
124 | rescue => e
125 | Logger.fatal "Mkdir #{tmp_file_path} failed."
126 | return [task_id, "fail",e]
127 | end
128 |
129 | # Check file format, support "tar.gz"
130 | # _file_name_prefix like "first_deploy-1.0.0.2"
131 | _file_name_prefix = url.split("/")[-1].split(".tar.gz")[0]
132 | _suffix = path.split(".")[-2..-1].join(".")
133 |
134 | if _suffix != "tar.gz"
135 | Logger.fatal "Unsupport file type. #{_suffix}"
136 | return [task_id, "fail","Unsupport file."]
137 | end
138 |
139 | # Download by url.
140 | deploy_file_name = _file_name_prefix + ".tar.gz"
141 | down_file_result = _down_file(tmp_file_path, url, deploy_file_name)
142 | if down_file_result[1] == "fail"
143 | return down_file_result
144 | else
145 | deploy_file = down_file_result[2]
146 | end
147 |
148 | # Unzip the package
149 | unzip_result = _do_unzip(deploy_file)
150 | if unzip_result[1] == "fail"
151 | return unzip_result
152 | else
153 | thor_data_path = unzip_result[2]
154 | end
155 |
156 | # Execute the thor script.
157 | ex_thor_result = _execute_thor(thor_data_path)
158 | return ex_thor_result
159 |
160 | end
161 |
162 | # Use this method, wen can start a deploy task.
163 | def _startDeploy(task_id, port, path, host_ip)
164 |
165 | begin
166 |
167 | Logger.info "[ Detach task #{task_id} ] Start......"
168 |
169 | # Make sure only one task runs at a time.
170 | lckself = File.new __FILE__
171 | unless lckself.flock File::LOCK_EX | File::LOCK_NB
172 | Logger.fatal "[ Detach task #{task_id} ] A task was running. Get LOCK #{__FILE__} failed,exit."
173 | return 1
174 | end
175 |
176 | Logger.info "[ Detach task #{task_id} ] Get LOCK #{__FILE__} succ. Start _do_deploy function."
177 |
178 | # Execute the deploy.
179 | result = _do_deploy(task_id, port, path, host_ip)
180 | return 0
181 |
182 | rescue => e
183 | Logger.fatal "[ Detach task #{task_id} ] Deploy #{task_id} exception : #{e} , exit."
184 | return 1
185 |
186 | ensure
187 | lckself.flock File::LOCK_UN
188 |
189 | Logger.info "[ Detach task #{task_id} ] Deploy finish. Result : #{result}\n#{LOG_SYMBOL*20} ENDTASK : #{task_id} #{LOG_SYMBOL*20}"
190 |
191 | end
192 |
193 | end
194 |
195 | # The RPC method
196 | def startDeploy(port, path)
197 |
198 | begin
199 |
200 | host_ip = request.ip
201 |
202 | # Create task id
203 | _part1 = _mk_id_part_hostname
204 | _part2 = _random_str
205 | _time_stamp = Time.now.strftime("%s")
206 | task_id = _part1 + _part2 + _time_stamp
207 |
208 | Logger.info "Task start.\n#{LOG_SYMBOL*20} task_id : #{task_id} #{LOG_SYMBOL*20}"
209 |
210 | if not FileTest::exists?(DEPLOY_LCK)
211 | cmd = "touch %s" %[DEPLOY_LCK]
212 | Logger.info "Touch file #{DEPLOY_LCK}"
213 | system(cmd)
214 | end
215 |
216 | # Make sure only one task runs at a time.
217 | dplck = File.new DEPLOY_LCK
218 | unless dplck.flock File::LOCK_EX | File::LOCK_NB
219 | Logger.fatal "A task was running. Get the LOCK #{DEPLOY_LCK} failed, exit."
220 | return [task_id, "fail", "A task was running"]
221 | end
222 |
223 | # Two flock.
224 | lckself = File.new __FILE__
225 | unless lckself.flock File::LOCK_EX | File::LOCK_NB
226 | Logger.fatal "A task was running. Get the LOCK #{__FILE__} failed, exit."
227 | return [task_id, "fail", "A task was running"]
228 | end
229 | lckself.flock File::LOCK_UN
230 |
231 | # Write the current task_id to the lock file
232 | File.open(DEPLOY_LCK, "w") do |f|
233 | Logger.info "Write the task_id : #{task_id} to the lock file."
234 | f.puts task_id
235 | end
236 |
237 | # Touch thor log first.
238 | thor_log_path = LOG_PATH + "thor_" + task_id + ".log"
239 | `touch #{thor_log_path}`
240 |
241 | # Do deploy, detach the deploy process.
242 | read, write = IO.pipe
243 | pid = fork { _startDeploy(task_id, port, path, host_ip) }
244 | Process.detach(pid)
245 | write.close
246 | read.close
247 | Logger.info "Detach task #{task_id} succ. Pid : #{pid}"
248 |
249 | rescue => e
250 | Logger.fatal "Deploy #{task_id} exception : #{e}"
251 | return [task_id, "fail", "Exception : #{e}"]
252 |
253 | ensure
254 | dplck.flock File::LOCK_UN
255 | Logger.info "Release lock file #{DEPLOY_LCK}.\n"
256 | end
257 |
258 | return [task_id, "succ", "hahaha"]
259 |
260 | end
261 |
262 | # Parser the log, get the deploy useful info.
263 | def _get_deploy_info_from_log(log_path)
264 |
265 | info_list = []
266 | re = /\[{1}\@(.*?)\]{1}(.*)/
267 |
268 | if FileTest::zero?(log_path)
269 | sleep(2)
270 | end
271 |
272 | if FileTest::zero?(log_path)
273 | info_list = ["[@start:init]"]
274 | end
275 |
276 | File.open(log_path).grep(/^\[\@/) do |line|
277 | md = re.match(line)
278 | list_s = []
279 | # The header, split it by :
280 | header = md[1].strip.chomp.chomp(",").delete(" ")
281 | list_s << header.split(":")
282 | # The content
283 | if md[2].strip.length > 0
284 | list_s << md[2].strip.chomp
285 | end
286 | info_list << list_s
287 | end
288 |
289 | return info_list
290 |
291 | end
292 |
293 | # Get deploy status from info.
294 | def _get_task_deploy_status_from_info(info)
295 |
296 | # If check env was not pass.
297 | for i in info
298 | return [["jobs", -2, "thor deploy check_env failed!"]] if i[0] == ["fail_mod", "check_env"]
299 | end
300 |
301 | mods = []
302 | # Collect result
303 | result_d = {}
304 | for i in info
305 | mods = i[0][1].split(",") if i[0][0] == "jobs"
306 | if mods
307 | result_d["jobs"] = mods
308 | for mod in mods
309 | result_d[mod] = i[0][0] if i[0][1] == mod
310 | end
311 | end
312 | end
313 |
314 | mods.each {|element| result_d[element] = "waiting" if not result_d.has_key?(element)}
315 |
316 | # -1 means no mod detail presents.
317 | result_l = [["jobs", mods.length == 0 ? -1 : mods.length]]
318 | # result_l finally like :
319 | # [['jobs', 3], ['xbox', 'succ']]
320 | for mod in mods
321 | _status = result_d[mod]
322 | if _status == "start_mod"
323 | result_l << [mod,"start"]
324 | elsif _status == "deploying"
325 | result_l << [mod,"deploying"]
326 | elsif _status == "succ_mod"
327 | result_l << [mod,"succ"]
328 | elsif _status == "fail_mod"
329 | result_l << [mod,"fail"]
330 | else
331 | result_l << [mod,"waiting"]
332 | end
333 | end
334 | return result_l
335 | end
336 |
337 | # Get a mod`s deploy detail log.
338 | def _get_mod_deploy_log(info, mod_name)
339 | result_list = [[mod_name]]
340 | for i in info
341 | result_list << i[1] if i[0][1] == mod_name and i[0][0] == "deploying"
342 | end
343 | return result_list
344 | end
345 |
346 | # Get a task`s deploy status.
347 | def getDeployTaskStatus(task_id)
348 |
349 | Logger.info "\n#{LOG_SYMBOL*29} getDeployTaskStatus #{LOG_SYMBOL*28}"
350 |
351 | thor_log_path = LOG_PATH + "thor_" + task_id + ".log"
352 | log_lines = `wc -l #{thor_log_path}|awk '{print $1}' `.chomp.to_s
353 |
354 | if not FileTest::exists?(thor_log_path)
355 | Logger.fatal "The thor log #{thor_log_path} not exist.\n#{LOG_SYMBOL*27} getDeployTaskStatus END #{LOG_SYMBOL*26}"
356 | return [["jobs", -2, "The thor log not exist."]]
357 | end
358 |
359 | begin
360 | info = _get_deploy_info_from_log(thor_log_path)
361 | result_list = _get_task_deploy_status_from_info(info)
362 | Logger.info "result_list : #{result_list}"
363 | rescue => e
364 | Logger.fatal "Get #{task_id} task status fail. Exception : #{e}"
365 | return [["jobs", -2, "Exception : #{e}"]]
366 | end
367 |
368 | Logger.info "Get #{task_id} task status succ. Log lines : #{log_lines}. Result : #{result_list} \n#{LOG_SYMBOL*27} getDeployLog4Mod END #{LOG_SYMBOL*26}"
369 | return result_list
370 |
371 | end
372 |
373 | # Get a deploy task of mod`s detail log.
374 | def getDeployLog4Mod(task_id, mod_name)
375 |
376 | Logger.info "\n#{LOG_SYMBOL*28} getDeployLog4Mod #{LOG_SYMBOL*28}"
377 |
378 | thor_log_path = LOG_PATH + "thor_" + task_id + ".log"
379 | if not FileTest::exists?(thor_log_path)
380 | Logger.fatal "Get #{task_id} task for mod #{mod_name} fail. The thor log #{thor_log_path} was not exists."
381 | return [["jobs", -2, "The thor log not exist."]]
382 | end
383 |
384 | begin
385 | info = _get_deploy_info_from_log(thor_log_path)
386 | result_list = _get_mod_deploy_log(info, mod_name)
387 | rescue => e
388 | Logger.fatal "Get #{task_id} task for mod #{mod_name} fail. Exception : #{e}"
389 | return [["jobs", -2, "Exception : #{e}"]]
390 | end
391 |
392 | Logger.info "Get #{task_id} task for mod #{mod_name} succ. Result : #{result_list} \n#{LOG_SYMBOL*26} getDeployLog4Mod END #{LOG_SYMBOL*26}"
393 | return result_list
394 |
395 | end
396 |
397 | # Get the global deploy status.
398 | def getDeployStatus()
399 | r_notask = [["jobs", 0, "No task running."]]
400 |
401 | lckself = File.new __FILE__
402 | if lckself.flock File::LOCK_EX | File::LOCK_NB
403 | lckself.flock File::LOCK_UN
404 |
405 | Logger.info "\n#{LOG_SYMBOL*31} getDeployStatus #{LOG_SYMBOL*30} \nGet global deploy status succ. Get lock and release LOCK #{__FILE__} succ. Result : #{r_notask}"
406 | return r_notask
407 |
408 | else
409 | task_id = File.readlines(DEPLOY_LCK)[0].strip
410 | result_list = getDeployTaskStatus(task_id)
411 |
412 | Logger.info "Get global deploy status succ. Get LOCK #{__FILE__} fail. Result : #{result_list}"
413 |
414 | return result_list
415 | end
416 | Logger.info "\n#{LOG_SYMBOL*29} getDeployStatus END #{LOG_SYMBOL*28}"
417 | end
418 |
419 | # Get a task`s full log.
420 | def getDeployLog( task_id)
421 |
422 | Logger.info "\n#{LOG_SYMBOL*32} getDeployLog #{LOG_SYMBOL*32}"
423 |
424 | thor_log_path = LOG_PATH + "thor_" + task_id + ".log"
425 | if not FileTest::exists?(thor_log_path)
426 | Logger.fatal "Cannot find the log file #{thor_log_path}"
427 | return [["jobs", -2, "The thor log not exist."]]
428 | end
429 |
430 | begin
431 | fulllog = File.readlines(thor_log_path)
432 | rescue => e
433 | Logger.fatal "Read log #{thor_log_path}, exception : #{e}"
434 | return [["jobs", -2, "Exception : #{e}"]]
435 | end
436 |
437 | log_lines = `wc -l #{thor_log_path}|awk '{print $1}' `.chomp.to_s
438 | Logger.info "Get deploy log succ. Log lines : #{log_lines}. Result :\n #{fulllog}"
439 | Logger.info "\n#{LOG_SYMBOL*30} getDeployLog END #{LOG_SYMBOL*30}"
440 |
441 | return fulllog
442 |
443 | end
444 |
445 | end # end of Deploy module
446 | end # end of RPC module
447 | end # end of Frigga module
448 |
--------------------------------------------------------------------------------
/static/vendor/bootstrap/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2012 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
--------------------------------------------------------------------------------
/views/static/vendor/bootstrap/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2012 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:''}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
--------------------------------------------------------------------------------