├── debian ├── compat ├── warp-minion.examples ├── warp-controller.dirs ├── warp-controller.examples ├── warp-minion.install ├── warp-controller.install ├── .gitignore ├── changelog ├── warp-controller.default ├── warp-minion.service ├── warp-controller.postinst ├── warp-minion.upstart ├── warp-controller.service ├── warp-controller.upstart ├── control └── rules ├── .travis.yml ├── .gitignore ├── src └── warp │ ├── client │ ├── state.cljs │ ├── app.cljs │ ├── layout.cljs │ ├── utils.cljs │ ├── router.cljs │ ├── models.cljs │ ├── ansi.cljs │ └── views.cljs │ ├── config.clj │ ├── ssl.clj │ ├── pipeline.clj │ ├── archive.clj │ ├── watcher.clj │ ├── dsl.clj │ ├── main.clj │ ├── parser.clj │ ├── scenario.clj │ ├── transport.clj │ ├── execution.clj │ ├── engine.clj │ └── api.clj ├── doc ├── warp-agent.json └── controller.clj ├── agent ├── warp-agent.go ├── Makefile └── src │ └── warp │ ├── config.go │ ├── environment.go │ ├── request.go │ ├── matcher.go │ ├── command.go │ └── client.go ├── resources ├── logback.xml └── public │ ├── index.html │ └── vendor │ └── bootstrap │ └── css │ ├── bootstrap-theme.min.css │ └── bootstrap.min.css ├── hubot ├── package.json ├── LICENSE ├── README.md ├── warp.coffee └── warp2.coffee ├── LICENSE ├── project.clj └── README.md /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/warp-minion.examples: -------------------------------------------------------------------------------- 1 | doc/warp-agent.json 2 | -------------------------------------------------------------------------------- /debian/warp-controller.dirs: -------------------------------------------------------------------------------- 1 | usr/bin 2 | var/log/warp 3 | -------------------------------------------------------------------------------- /debian/warp-controller.examples: -------------------------------------------------------------------------------- 1 | doc/controller.clj 2 | -------------------------------------------------------------------------------- /debian/warp-minion.install: -------------------------------------------------------------------------------- 1 | agent/warp-agent usr/sbin 2 | -------------------------------------------------------------------------------- /debian/warp-controller.install: -------------------------------------------------------------------------------- 1 | target/warp-*-standalone.jar usr/share/java 2 | -------------------------------------------------------------------------------- /debian/.gitignore: -------------------------------------------------------------------------------- 1 | /*.log 2 | /*.debhelper 3 | /*.substvars 4 | /*-stamp 5 | /files 6 | /warp*/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: clojure 3 | lein: lein 4 | jdk: 5 | - openjdk8 6 | branches: 7 | except: 8 | - gh-pages 9 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | warp (0.0-0) UNRELEASED; urgency=medium 2 | 3 | * Fake entry. 4 | 5 | -- Vincent Bernat Wed, 13 Apr 2016 15:24:53 +0200 6 | 7 | -------------------------------------------------------------------------------- /debian/warp-controller.default: -------------------------------------------------------------------------------- 1 | # -*- sh -*- 2 | 3 | JAVA=java 4 | JAVA_OPTS= 5 | CLASSPATH=/usr/share/java/warp-controller.jar 6 | 7 | export JAVA JAVA_OPTS CLASSPATH 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /site 9 | /.lein-* 10 | /.nrepl-port 11 | /agent/warp-agent 12 | /agent/pkg/ 13 | resources/public/warp 14 | -------------------------------------------------------------------------------- /src/warp/client/state.cljs: -------------------------------------------------------------------------------- 1 | (ns warp.client.state 2 | (:require [reagent.core :as r])) 3 | 4 | (def app 5 | "This is where we will keep all state" 6 | (r/atom {:route {:handler :scenario-list} 7 | :scenario {} 8 | :scenarios []})) 9 | -------------------------------------------------------------------------------- /doc/warp-agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "agent.example.com", 3 | "log": "syslog", 4 | "server": "controller.example.com:6380", 5 | "cacert": "/etc/warp/certs/ca-crt.pem", 6 | "cert": "/etc/warp/certs/agent.pem", 7 | "privkey": "/etc/warp/certs/agent.key.pem" 8 | } 9 | -------------------------------------------------------------------------------- /doc/controller.clj: -------------------------------------------------------------------------------- 1 | {:transport {:port 6380 2 | :ssl {:ca-cert "/etc/warp/certs/ca-crt.pem" 3 | :cert "/etc/warp/certs/server.pem" 4 | :pkey "/etc/warp/certs/server.pkey64.p8"}} 5 | :api {:port 6381} 6 | :scenarios "/etc/warp/scenarios" 7 | :keepalive 30} 8 | -------------------------------------------------------------------------------- /debian/warp-minion.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description="Warp agent" 3 | ConditionPathExists=/usr/sbin/warp-agent 4 | ConditionPathExists=/etc/warp/agent.json 5 | 6 | [Service] 7 | ExecStart=/usr/sbin/warp-agent /etc/warp/agent.json 8 | LimitNOFILE=8192 9 | Restart=on-failure 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /debian/warp-controller.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | case $1 in 6 | configure|reconfigure) 7 | adduser --system --disabled-password --disabled-login --home /var/log/warp \ 8 | --no-create-home --quiet --force-badname --group _warp 9 | chown _warp:adm /var/log/warp 10 | ;; 11 | esac 12 | 13 | #DEBHELPER# 14 | 15 | exit 0 16 | -------------------------------------------------------------------------------- /debian/warp-minion.upstart: -------------------------------------------------------------------------------- 1 | description "Warp agent" 2 | 3 | start on runlevel [2345] 4 | stop on runlevel [!2345] 5 | 6 | respawn 7 | respawn limit 5 60 8 | 9 | limit nofile 8192 8192 10 | 11 | pre-start script 12 | [ -x "/usr/sbin/warp-agent" ] || exit 0 13 | [ -r "/etc/warp/agent.json" ] || exit 0 14 | end script 15 | 16 | exec /usr/sbin/warp-agent /etc/warp/agent.json 17 | -------------------------------------------------------------------------------- /agent/warp-agent.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "fmt" 6 | "warp" 7 | ) 8 | 9 | func main() { 10 | if (len(os.Args) < 2) { 11 | fmt.Printf("not enough args, bye.\n") 12 | os.Exit(1) 13 | } 14 | 15 | cfg := warp.ReadConfig(os.Args[1]) 16 | client := warp.NewClient(cfg) 17 | client.WaitForQuit() 18 | client.Logger.Printf("client has quit, bye.") 19 | os.Exit(0) 20 | } 21 | -------------------------------------------------------------------------------- /resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/warp/config.clj: -------------------------------------------------------------------------------- 1 | (ns warp.config 2 | (:require [com.stuartsierra.component :as component] 3 | [clojure.edn :as edn] 4 | [clojure.tools.logging :refer [error info debug]])) 5 | 6 | (def default-logging 7 | {:pattern "%p [%d] %t - %c - %m%n" 8 | :external false 9 | :console true 10 | :files [] 11 | :level "info"}) 12 | 13 | (defn load-path 14 | [path] 15 | (-> (or path "/etc/warp/controller.clj") 16 | slurp 17 | edn/read-string)) 18 | -------------------------------------------------------------------------------- /debian/warp-controller.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description="Warp controller" 3 | ConditionPathExists=/usr/share/java/warp-controller.jar 4 | ConditionPathExists=/etc/warp/controller.clj 5 | 6 | [Service] 7 | Type=simple 8 | User=_warp 9 | Group=_warp 10 | EnvironmentFile=-/etc/default/warp-controller 11 | ExecStart=/usr/bin/java $JAVA_OPTS -cp ${CLASSPATH} warp.main -f /etc/warp/controller.clj 12 | LimitNOFILE=8192 13 | Restart=on-failure 14 | SuccessExitStatus=2 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /src/warp/ssl.clj: -------------------------------------------------------------------------------- 1 | (ns warp.ssl 2 | "Clojure glue code to interact with the horrible JVM SSL code" 3 | (:require [clojure.java.io :as io]) 4 | (:import io.netty.handler.ssl.ClientAuth 5 | io.netty.handler.ssl.SslContextBuilder)) 6 | 7 | (defn server-context 8 | "Build an SSL client context for netty" 9 | [{:keys [pkey cert ca-cert]}] 10 | (-> (SslContextBuilder/forServer (io/file cert) (io/file pkey)) 11 | (.trustManager (io/file ca-cert)) 12 | (.clientAuth ClientAuth/REQUIRE) 13 | (.build))) 14 | -------------------------------------------------------------------------------- /debian/warp-controller.upstart: -------------------------------------------------------------------------------- 1 | description "Warp controller" 2 | 3 | start on runlevel [2345] 4 | stop on runlevel [!2345] 5 | 6 | respawn 7 | respawn limit 5 60 8 | normal exit 0 2 9 | 10 | limit nofile 8192 8192 11 | setuid _warp 12 | setgid _warp 13 | 14 | pre-start script 15 | [ -r "/usr/share/java/warp-controller.jar" ] || exit 0 16 | [ -r "/etc/warp/controller.clj" ] || exit 0 17 | end script 18 | 19 | script 20 | [ -r /etc/default/warp-controller ] && . /etc/default/warp-controller 21 | exec ${JAVA} ${JAVA_OPTS} -cp ${CLASSPATH} warp.main -f /etc/warp/controller.clj 22 | end script 23 | -------------------------------------------------------------------------------- /src/warp/pipeline.clj: -------------------------------------------------------------------------------- 1 | (ns warp.pipeline 2 | (:import java.util.concurrent.TimeUnit 3 | java.nio.ByteOrder 4 | io.netty.handler.timeout.ReadTimeoutHandler 5 | io.netty.handler.codec.LengthFieldBasedFrameDecoder 6 | io.netty.handler.codec.LengthFieldPrepender)) 7 | 8 | (defn read-timeout 9 | [timeout] 10 | (ReadTimeoutHandler. (long timeout) TimeUnit/SECONDS)) 11 | 12 | (defn length-decoder 13 | [] 14 | (LengthFieldBasedFrameDecoder. ByteOrder/BIG_ENDIAN 16384 0 4 0 4 true)) 15 | 16 | (defn length-encoder 17 | [] 18 | (LengthFieldPrepender. ByteOrder/BIG_ENDIAN 4 0 true)) 19 | -------------------------------------------------------------------------------- /hubot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hubot-warp", 3 | "description": "A hubot script to interact with warp https://github.com/pyr/warp", 4 | "version": "0.5.0", 5 | "author": "Pierre-Yves Ritschard", 6 | "license": "MIT", 7 | "keywords": [ 8 | "hubot", 9 | "hubot-scripts" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:pyr/hubot-warp.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/pyr/warp/issues" 17 | }, 18 | "dependencies": { 19 | "coffee-script": "~1.6" 20 | }, 21 | "main": "hubot/scripts/warp.coffee" 22 | } 23 | -------------------------------------------------------------------------------- /agent/Makefile: -------------------------------------------------------------------------------- 1 | VERSION=0.3.0-snapshot 2 | PREFIX?=/usr/local 3 | GOPATH=$(PWD)/build:$(PWD) 4 | PROGRAM=warp-agent 5 | GO=env GOPATH=$(GOPATH) go 6 | SRCS= src/warp/matcher.go \ 7 | src/warp/environment.go \ 8 | src/warp/request.go \ 9 | src/warp/client.go \ 10 | src/warp/config.go \ 11 | src/warp/command.go 12 | 13 | RM?=rm -f 14 | LN=ln -s 15 | MAIN=warp-agent.go 16 | 17 | all: $(PROGRAM) 18 | 19 | $(PROGRAM): $(MAIN) $(SRCS) 20 | $(GO) build -o $(PROGRAM) $(MAIN) 21 | 22 | clean: 23 | $(RM) $(PROGRAM) 24 | $(GO) clean 25 | 26 | cleandeps: clean 27 | $(RM) $(PWD)/build 28 | 29 | #deps: 30 | # $(GO) get gopkg.in/yaml.v2 31 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: warp 2 | Priority: optional 3 | Section: net 4 | Maintainer: vbe@exoscale.ch 5 | Build-Depends: debhelper (>= 9), 6 | default-jre-headless (>= 1:1.8) | java7-runtime-headless | java8-runtime-headless, 7 | lsb-release, 8 | leiningen, 9 | golang, 10 | dh-systemd 11 | 12 | Package: warp-controller 13 | Architecture: all 14 | Depends: ${misc:Depends}, 15 | default-jre-headless (>= 1:1.8) | java7-runtime-headless | java8-runtime-headless, 16 | adduser 17 | Description: Warp commander (daemon/controller) 18 | 19 | Package: warp-minion 20 | Architecture: any 21 | Depends: ${misc:Depends}, ${shlibs:Depends} 22 | Description: Warp agent (client/agent) 23 | -------------------------------------------------------------------------------- /agent/src/warp/config.go: -------------------------------------------------------------------------------- 1 | package warp 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | ) 9 | 10 | type Config struct { 11 | Host string `json:"host" binding:"required"` 12 | Cert string `json:"cert" binding:"required"` 13 | CaCert string `json:"cacert" binding:"required"` 14 | PrivKey string `json:"privkey" binding:"required"` 15 | Server string `json:"server" binding:"required"` 16 | LogTo string `json:"log" binding:"required"` 17 | } 18 | 19 | func ReadConfig(path string) Config { 20 | 21 | data, err := ioutil.ReadFile(path) 22 | if err != nil { 23 | fmt.Printf("cannot read configuration: %v", err) 24 | os.Exit(1) 25 | } 26 | 27 | var cfg Config 28 | json.Unmarshal(data, &cfg) 29 | 30 | return cfg 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Pierre-Yves Ritschard 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /hubot/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Pierre-Yves Ritschard 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /src/warp/client/app.cljs: -------------------------------------------------------------------------------- 1 | (ns warp.client.app 2 | (:require [warp.client.views :as views] 3 | [warp.client.router :refer [set-router! route-dispatcher]] 4 | [warp.client.models :refer [start-sync!]] 5 | [reagent.core :refer [render]])) 6 | 7 | 8 | (enable-console-print!) 9 | 10 | (defn ^:export run 11 | "Our entrypoint, renders the main component in a predefined location" 12 | [] 13 | (set-router! 14 | ["/" {"" :scenario-list 15 | ["scenario/" :id] :scenario-detail 16 | "replay" {["/" :id "/" :host] :client-detail 17 | ["/" :id] :replay-detail}}] 18 | {:scenario-list views/scenario-list 19 | :scenario-detail views/scenario-detail 20 | :replay-detail views/replay-detail 21 | :client-detail views/client-detail 22 | :default views/scenario-list}) 23 | (start-sync!) 24 | (render [route-dispatcher] (js/document.getElementById "app"))) 25 | -------------------------------------------------------------------------------- /hubot/README.md: -------------------------------------------------------------------------------- 1 | hubot-warp: command and control from your chat room 2 | =================================================== 3 | 4 | This hubot script allows you to control warp executions 5 | from your chat room. 6 | 7 | ## Configuration 8 | 9 | The script will rely on two variables: 10 | 11 | - `HUBOT_WARP_URL`: Where to send warp requests to 12 | - `HUBOT_WARP_SHOW_URL`: If necessary, what url to use for display 13 | 14 | ## Invocation 15 | 16 | Hubot understands the following invocation: 17 | 18 | - `hubot: warp me ` 19 | - `hubot: engage! ` 20 | - `, engage!` 21 | 22 | ## Command specification 23 | 24 | Warp commands may have a profile (with an optional arg) supplied as well 25 | as parameters. The full command specification is: 26 | 27 | ``` 28 | command (to profile param) (with param1 param2) 29 | ``` 30 | 31 | For instance, if you have a parameterized *platform* profile for the deploy command, you would say: 32 | 33 | ``` 34 | deploy to platform staging, engage! 35 | ``` 36 | -------------------------------------------------------------------------------- /src/warp/archive.clj: -------------------------------------------------------------------------------- 1 | (ns warp.archive 2 | (:require [com.stuartsierra.component :as com])) 3 | 4 | (defprotocol Archive 5 | (record [this scenario id event]) 6 | (all-replays [this]) 7 | (replays [this scenario]) 8 | (replay [this id])) 9 | 10 | (defn set-conj 11 | [v e] 12 | (if (nil? v) #{e} (conj v e))) 13 | 14 | (defrecord MemoryArchive [db] 15 | com/Lifecycle 16 | (start [this] 17 | (assoc this :db (atom {}))) 18 | (stop [this] 19 | (assoc this :db nil)) 20 | Archive 21 | (record [this scenario id execution] 22 | (swap! db #(-> % 23 | (assoc-in [:replays id] (dissoc execution :listener)) 24 | (update-in [:by-scenario (keyword scenario)] set-conj id)))) 25 | (replays [this scenario] 26 | (vec (get-in @db [:by-scenario (keyword scenario)]))) 27 | (replay [this id] 28 | (get-in @db [:replays id])) 29 | (all-replays [this] 30 | (vals (:replays @db)))) 31 | 32 | (defn make-archive 33 | [] 34 | (map->MemoryArchive {})) 35 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DISTRIBUTION := $(shell lsb_release -sc) 4 | VERSION := $(shell head -n1 project.clj | awk '{print $$3}') 5 | DEBVERSION := $(subst -SNAPSHOT,~$(shell date +%Y%m%d-%H%M%S),$(VERSION))-0~$(DISTRIBUTION)3 6 | 7 | ifeq ($(shell dpkg-vendor --derives-from Ubuntu && echo yes),yes) 8 | build_controller=yes 9 | else 10 | build_controller=no 11 | endif 12 | 13 | %: 14 | ifeq ($(build_controller),yes) 15 | dh $@ --with systemd 16 | else 17 | dh $@ --with systemd --package=warp-minion 18 | endif 19 | 20 | override_dh_gencontrol: 21 | dh_gencontrol -- -v$(DEBVERSION) 22 | 23 | # Don't try to do anything automatically 24 | override_dh_auto_configure: 25 | override_dh_auto_test: 26 | override_dh_auto_install: 27 | 28 | # Too long on JAR files 29 | override_dh_strip_nondeterminism: 30 | 31 | override_dh_auto_build: $(info I: DEBVERSION=$(DEBVERSION)) 32 | ifeq ($(build_controller),yes) 33 | lein uberjar 34 | endif 35 | cd agent && make 36 | 37 | override_dh_link: 38 | ifeq ($(build_controller),yes) 39 | dh_link usr/share/java/warp-$(VERSION)-standalone.jar usr/share/java/warp-controller.jar 40 | endif 41 | -------------------------------------------------------------------------------- /src/warp/client/layout.cljs: -------------------------------------------------------------------------------- 1 | (ns warp.client.layout 2 | (:require [clojure.string :as str])) 3 | 4 | (defn h4 5 | [& fragments] 6 | [:h4 (str/join " " fragments)]) 7 | 8 | (defn h3 9 | [& fragments] 10 | [:h3 (str/join " " fragments)]) 11 | 12 | (defn h2 13 | [& fragments] 14 | [:h2 (str/join " " fragments)]) 15 | 16 | (defn h1 17 | [& fragments] 18 | [:h1 (str/join " " fragments)]) 19 | 20 | (defn link-to 21 | [url link] 22 | [:a {:href url} link]) 23 | 24 | (defn code 25 | [& fragments] 26 | [:code (str/join " " fragments)]) 27 | 28 | (defn console 29 | [& fragments] 30 | [:pre {:class "console"} (str/join " " fragments)]) 31 | 32 | (defn tr 33 | [& row] 34 | [:tr 35 | (for [[i td] (map-indexed vector row)] 36 | ^{:key (str "tr-" i)} [:td td])]) 37 | 38 | (defn panel 39 | [title content] 40 | [:div {:class "panel panel-default"} 41 | [:div {:class "panel-heading"} 42 | [:h3 {:class "modal-title panel-title"} title]] 43 | [:div {:class "panel-body"} 44 | content]]) 45 | 46 | (defn table-striped 47 | [columns rows] 48 | [:table {:class "table table-striped"} 49 | [:thead 50 | [:tr (for [[i th] (map-indexed vector columns)] 51 | ^{:key (str "th-" i)} [:th th])]] 52 | [:tbody rows]]) 53 | -------------------------------------------------------------------------------- /src/warp/client/utils.cljs: -------------------------------------------------------------------------------- 1 | (ns warp.client.utils 2 | (:import goog.History) 3 | (:require [warp.client.ansi :refer [highlight]] 4 | [clojure.string :refer [join split]])) 5 | 6 | (defn json-stringify 7 | [value] 8 | (js/JSON.stringify (clj->js value))) 9 | 10 | (defn paren 11 | ([prefix content] 12 | (str prefix "(" content ")")) 13 | ([content] 14 | (paren "" content))) 15 | 16 | (defn first-line 17 | [s] 18 | (let [[line] (split s #"\n")] 19 | line)) 20 | 21 | (defn pretty-match 22 | [{:keys [type host fact value clause clauses] :as matcher}] 23 | (println "pretty matching:" type " => " (pr-str matcher)) 24 | (case type 25 | "all" "all" 26 | "none" "none" 27 | "host" (str "host = " host) 28 | "fact" (str "facts[" fact "] =" value) 29 | "not" (paren "!" (pretty-match clause)) 30 | "and" (paren (join " && " (map pretty-match clauses))) 31 | "or" (paren (join " || " (map pretty-match clauses))) 32 | "")) 33 | 34 | (defn highlight-datum 35 | [datum] 36 | (assoc datum 37 | "stderr" (highlight (get datum "stderr")) 38 | "stdout" (highlight (get datum "stdout")))) 39 | 40 | (defn ansi-colors 41 | [[hostname data]] 42 | [hostname (map highlight-datum data)]) 43 | 44 | (defn add-scripts 45 | [scripts [host steps]] 46 | [host 47 | (map-indexed (fn [index item] (assoc item "script" (scripts index nil))) 48 | (vec steps))]) 49 | -------------------------------------------------------------------------------- /src/warp/client/router.cljs: -------------------------------------------------------------------------------- 1 | (ns warp.client.router 2 | (:require-macros [cljs.core.async.macros :refer [go go-loop]]) 3 | (:require [goog.events :as events] 4 | [goog.history.EventType :as EventType] 5 | [cljs.core.async :as a] 6 | [warp.client.state :refer [app]] 7 | [bidi.bidi :refer [match-route]]) 8 | (:import goog.History)) 9 | 10 | (defonce route-events 11 | (a/chan 10)) 12 | 13 | (def router 14 | (atom {:routes nil :handlers nil})) 15 | 16 | (defn set-router! 17 | [routes handlers] 18 | (reset! router {:routes routes :handlers handlers})) 19 | 20 | (defn route-update 21 | [event] 22 | (let [token (.-token event)] 23 | (a/put! route-events token))) 24 | 25 | (defonce history 26 | (doto (History.) 27 | (events/listen EventType/NAVIGATE route-update) 28 | (.setEnabled true))) 29 | 30 | (defn route-dispatcher 31 | [] 32 | (go-loop [location (a/path close]] 7 | [warp.parser :refer [load-unit]] 8 | [clojure.tools.logging :refer [info]])) 9 | 10 | (defprotocol ScenarioStore 11 | (by-id [this id]) 12 | (all-scenarios [this])) 13 | 14 | (defn extract-id 15 | [{:keys [type path]}] 16 | (and (= type :path) 17 | (when-let [[_ id] (re-find #"(?i)^([^.].*)\.(clojure|edn|clj|warp|wrp)$" (str path))] 18 | (keyword id)))) 19 | 20 | (defn load-dir 21 | [db dir] 22 | (info "loading dir" dir) 23 | (let [dir (io/file dir)] 24 | (doseq [^File file (file-seq dir) 25 | :when (.isFile file) 26 | :let [path (.relativize (.toPath dir) (.toPath file))]] 27 | (when-let [id (extract-id {:type :path :path path})] 28 | (when-not (re-find #"/" (name id)) 29 | (info "found scenario: " (name id)) 30 | (swap! db assoc id (assoc (load-unit file) :id id))))))) 31 | 32 | (defn on-change-fn 33 | [dir db] 34 | (fn [{:keys [type path types] :as ev}] 35 | (info "filesystem event: " (pr-str ev)) 36 | (when-let [id (extract-id ev)] 37 | (case (last (remove #{:overflow} (:types ev))) 38 | :delete (swap! db dissoc id) 39 | :modify (swap! db assoc id (load-unit (->path dir path))) 40 | :create (swap! db assoc id (load-unit (->path dir path))))))) 41 | 42 | (defrecord Watcher [server db directory] 43 | ScenarioStore 44 | (by-id [this id] 45 | (get @db (keyword id))) 46 | (all-scenarios [this] 47 | (vals @db)) 48 | com/Lifecycle 49 | (start [this] 50 | (let [internal-db (atom {}) 51 | f (on-change-fn directory internal-db)] 52 | (load-dir internal-db directory) 53 | (assoc this :server (watch! directory f) :db internal-db))) 54 | (stop [this] 55 | (close server) 56 | (assoc this :db nil :server nil))) 57 | 58 | 59 | (defn make-watcher 60 | [scenario-dir] 61 | (map->Watcher {:directory scenario-dir})) 62 | -------------------------------------------------------------------------------- /src/warp/client/ansi.cljs: -------------------------------------------------------------------------------- 1 | (ns warp.client.ansi 2 | (:require [clojure.string :as string])) 3 | 4 | (def colors 5 | ["black" 6 | "red" 7 | "green" 8 | "yellow" 9 | "blue" 10 | "magenta" 11 | "cyan" 12 | "white"]) 13 | 14 | (def replacements 15 | {"&" "&" 16 | "<" "<" 17 | ">" ">"}) 18 | 19 | (defn escape 20 | [text] 21 | (string/replace 22 | text 23 | (re-pattern (str "[" (string/join "" (keys replacements)) "]")) 24 | #(replacements %1))) 25 | 26 | (defn linkify 27 | [text] 28 | (string/replace 29 | text 30 | #"(https?://[^\s]+)" 31 | "$1")) 32 | 33 | (defn within-bounds 34 | [[start end] value] 35 | (and (>= value start) 36 | (< value end))) 37 | 38 | (defn style 39 | [code] 40 | (let [index (mod code 10) 41 | color (nth colors index)] 42 | (condp within-bounds code 43 | [0 1] {:background nil :bold nil :color nil} 44 | [1 2] {:bold "bold"} 45 | [30 38] {:color color} 46 | [39 40] {:color nil} 47 | [40 48] {:background (str "background-" color)} 48 | [90 98] {:color (str "bright-" color)} 49 | [100 108] {:background (str "background-bright-" color)}))) 50 | 51 | (defn get-classes 52 | [styles] 53 | (string/join " " (remove nil? (vals styles)))) 54 | 55 | (defn process-parts 56 | [styles [part & parts] done] 57 | (let [lines (string/split part "\n" -1) 58 | [_ codes text] (re-matches #"([\d;]+?)?m(.*)" (first lines)) 59 | text (str (or text (first lines)) 60 | (if (empty? (rest lines)) "" "\n") 61 | (string/join "\n" (rest lines))) 62 | next-styles (if (nil? codes) 63 | {} 64 | (apply merge (map (comp style int) 65 | (string/split codes ";")))) 66 | styles (merge styles next-styles) 67 | classes (get-classes styles) 68 | done (if (empty? classes) 69 | (str done text) 70 | (str done (str "" text "")))] 71 | (if (empty? parts) 72 | done 73 | (recur styles parts done)))) 74 | 75 | (defn highlight 76 | [text] 77 | (when-not (empty? text) 78 | (let [text (-> text 79 | (escape) 80 | (linkify)) 81 | parts (string/split text #"\033\[")] 82 | (process-parts {} parts "")))) 83 | -------------------------------------------------------------------------------- /agent/src/warp/environment.go: -------------------------------------------------------------------------------- 1 | package warp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "os/exec" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type Environment interface { 15 | Host() string 16 | Lookup(string) string 17 | } 18 | 19 | type MapEnvironment struct { 20 | internal map[string]string 21 | lock sync.RWMutex 22 | } 23 | 24 | func (me MapEnvironment) Host() string { 25 | me.lock.RLock() 26 | defer me.lock.RUnlock() 27 | return me.internal["host"] 28 | } 29 | 30 | func (me MapEnvironment) Lookup(k string) string { 31 | me.lock.RLock() 32 | defer me.lock.RUnlock() 33 | return me.internal[k] 34 | } 35 | 36 | func NewEnvironment(host string) *MapEnvironment { 37 | return &MapEnvironment{ 38 | internal: map[string]string{ 39 | "host": host, 40 | }, 41 | } 42 | } 43 | 44 | func updatePayload(payload map[string]string) { 45 | for { 46 | time.Sleep(10 * time.Second) 47 | } 48 | } 49 | 50 | func PrefixedKey(prefix string, k string) string { 51 | if prefix == "" { 52 | return k 53 | } else { 54 | return fmt.Sprintf("%s.%s", prefix, k) 55 | } 56 | } 57 | 58 | func BuildPrefixedEnv(env map[string]string, prefix string, values map[string]interface{}) { 59 | 60 | for k, v := range values { 61 | pk := PrefixedKey(prefix, k) 62 | switch x := v.(type) { 63 | case map[string]interface{}: 64 | BuildPrefixedEnv(env, fmt.Sprintf(pk), x) 65 | default: 66 | env[pk] = fmt.Sprintf("%v", x) 67 | } 68 | } 69 | } 70 | 71 | func BuildEnv(logger *log.Logger, host string, env *MapEnvironment) { 72 | 73 | for { 74 | stdin := strings.NewReader("") 75 | var out bytes.Buffer 76 | var errout bytes.Buffer 77 | cmd := exec.Command("facter", "-j", "--external-dir", "/etc/facter/facts.d") 78 | cmd.Stdin = stdin 79 | cmd.Stdout = &out 80 | cmd.Stderr = &errout 81 | outmap := make(map[string]interface{}) 82 | 83 | err := cmd.Run() 84 | if err != nil { 85 | log.Printf("could not refresh environment: %v", err) 86 | return 87 | } 88 | 89 | err = json.Unmarshal(out.Bytes(), &outmap) 90 | if err != nil { 91 | log.Printf("could not refresh environment: %v", err) 92 | return 93 | } 94 | 95 | env.lock.Lock() 96 | for k := range env.internal { 97 | delete(env.internal, k) 98 | } 99 | env.internal["host"] = host 100 | BuildPrefixedEnv(env.internal, "facter", outmap) 101 | env.lock.Unlock() 102 | time.Sleep(120 * time.Second) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Warp 5 | 6 | 7 | 8 | 50 | 51 | 52 | 55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/warp/dsl.clj: -------------------------------------------------------------------------------- 1 | (ns warp.dsl 2 | (:require [clojure.string :as str] 3 | [stevedore.bash :as bash]) 4 | (:refer-clojure :exclude [and or not])) 5 | 6 | (defn timeout [seconds] {:timeout seconds}) 7 | 8 | (defn matcher [content] {:matcher content}) 9 | (defn and [& clauses] {:type :and :clauses clauses}) 10 | (defn or [& clauses] {:type :or :clauses clauses}) 11 | (defn not [clause] {:type :not :clause clause}) 12 | (defn host [host] {:type :host :host host}) 13 | (defn fact [fk fv] {:type :fact :fact fk :value fv}) 14 | (defn facts [facts] 15 | {:type :and :clauses (mapv #(fact (key %) (val %)) facts)}) 16 | (defn all [] {:type :all}) 17 | (defn none [] {:type :none}) 18 | 19 | (defn commands [& content] {:commands content}) 20 | (defn ping [] {:type :ping}) 21 | (defn sleep [seconds] {:type :sleep :seconds seconds}) 22 | 23 | (defn service* 24 | ([srv] (service* srv :status)) 25 | ([srv action] {:type :service 26 | :service (name srv) 27 | :action (name action)})) 28 | (defmacro service 29 | ([srv] 30 | `(service* (name (quote ~srv)))) 31 | ([srv action] 32 | `(service* (name (quote ~srv)) (name (quote ~action))))) 33 | 34 | (defn shell 35 | [& content] 36 | (reduce merge {:type :shell} content)) 37 | 38 | 39 | (defn exits 40 | [& exits] 41 | {:exits (vec (flatten exits))}) 42 | 43 | (defn cwd 44 | [dir] 45 | {:cwd dir}) 46 | 47 | (defmacro script 48 | [& forms] 49 | `{:shell (bash/script ~@forms)}) 50 | 51 | (defn scenario* 52 | [script-name directives] 53 | (reduce merge 54 | {:timeout 120 55 | :matcher {:type :none} 56 | :profiles {} 57 | :name (name script-name)} 58 | directives)) 59 | 60 | (defn profiles 61 | [& profiles] 62 | {:profiles 63 | (reduce merge {} profiles)}) 64 | 65 | (defmacro profile 66 | [pname content] 67 | (hash-map pname content)) 68 | 69 | (defmacro defscenario 70 | [sym & directives] 71 | (let [dirs (vec directives)] 72 | `(scenario* (quote ~sym) ~dirs))) 73 | 74 | (defn read-strings 75 | "Returns a sequence of forms read from string." 76 | ([string] 77 | (read-strings [] 78 | (-> string (java.io.StringReader.) 79 | (clojure.lang.LineNumberingPushbackReader.)))) 80 | ([forms reader] 81 | (let [form (clojure.lang.LispReader/read reader false ::EOF false)] 82 | (if (= ::EOF form) 83 | forms 84 | (recur (conj forms (binding [*ns* (find-ns 'warp.dsl)] (eval form))) 85 | reader))))) 86 | 87 | (defn load-scenario 88 | [path] 89 | (when-let [[_ sname] (re-matches #"(?i)^.*/(.*)\.wa?rp" path)] 90 | (scenario* (str/lower-case sname) (read-strings (slurp path))))) 91 | -------------------------------------------------------------------------------- /src/warp/main.clj: -------------------------------------------------------------------------------- 1 | (ns warp.main 2 | (:gen-class) 3 | (:require [com.stuartsierra.component :as component] 4 | [warp.config :refer [load-path default-logging]] 5 | [warp.archive :refer [make-archive]] 6 | [warp.engine :refer [make-engine]] 7 | [warp.api :refer [make-api]] 8 | [warp.transport :refer [make-transport make-mux]] 9 | [warp.watcher :refer [make-watcher]] 10 | [signal.handler :refer [with-handler]] 11 | [unilog.config :refer [start-logging!]] 12 | [spootnik.uncaught :refer [uncaught]] 13 | [clojure.tools.logging :refer [info warn]] 14 | [clojure.tools.cli :refer [cli]])) 15 | 16 | (set! *warn-on-reflection* true) 17 | 18 | (defn get-cli 19 | [args] 20 | (try 21 | (cli args 22 | ["-h" "--help" "Show help" :default false :flag true] 23 | ["-f" "--path" "Configuration file path" :default nil]) 24 | (catch Exception e 25 | (binding [*out* *err*] 26 | (println "Could not parse arguments: " (.getMessage e))) 27 | (System/exit 1)))) 28 | 29 | (defn config->system 30 | [path] 31 | (try 32 | (let [config (load-path path)] 33 | 34 | (start-logging! (merge default-logging (:logging config))) 35 | 36 | (-> (component/system-map :api (make-api (:api config)) 37 | :watcher (make-watcher (:scenarios config)) 38 | :mux (make-mux) 39 | :engine (make-engine (:keepalive config)) 40 | :transport (make-transport (:transport config)) 41 | :archive (make-archive)) 42 | (component/system-using {:api [:engine :archive :watcher] 43 | :engine [:transport :archive :watcher :mux] 44 | :transport [:mux]}))))) 45 | 46 | (uncaught e (warn e "uncaught exception")) 47 | 48 | (defn -main 49 | [& args] 50 | (let [[{:keys [path help]} args banner] (get-cli args)] 51 | 52 | (when help 53 | (println banner) 54 | (System/exit 0)) 55 | 56 | (let [system (atom (config->system path))] 57 | 58 | (with-handler :term 59 | (info "Caught SIGTERM, quitting") 60 | (component/stop-system @system) 61 | (System/exit 0)) 62 | 63 | (with-handler :hup 64 | (info "Caught SIGHUP, reloading") 65 | (swap! system (comp component/start-system 66 | component/stop-system))) 67 | 68 | (info "ready to start the system") 69 | (swap! system component/start-system))) 70 | nil) 71 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject warp "0.7.2-SNAPSHOT" 2 | :main warp.main 3 | :plugins [[lein-cljsbuild "1.1.7"] 4 | [lein-ancient "0.6.15"]] 5 | :dependencies [[org.clojure/clojure "1.10.1"] 6 | [org.clojure/tools.logging "0.4.0"] 7 | [org.clojure/tools.cli "0.3.5"] 8 | [org.clojure/core.async "0.4.500"] 9 | [com.stuartsierra/component "0.4.0"] 10 | [spootnik/unilog "0.7.25"] 11 | [spootnik/uncaught "0.5.5"] 12 | [spootnik/signal "0.2.2"] 13 | [spootnik/watchman "0.3.7"] 14 | [spootnik/stevedore "0.9.1"] 15 | [aleph "0.4.6"] 16 | [gloss "0.2.6"] 17 | [bidi "2.1.6"] 18 | [cheshire "5.8.1"] 19 | [org.clojure/clojurescript "1.10.520"] 20 | [reagent "0.8.1"] 21 | [cljs-http "0.1.46"] 22 | [ring/ring-core "1.7.1"] 23 | [io.netty/netty-all "4.1.37.Final"] 24 | [io.netty/netty-tcnative "2.0.25.Final"] 25 | [org.javassist/javassist "3.25.0-GA"] 26 | [io.netty/netty-tcnative-boringssl-static "2.0.25.Final"] 27 | [cc.qbits/alia "4.3.1"] 28 | ] 29 | :pedantic? :warn 30 | :deploy-repositories [["releases" :clojars] ["snapshots" :clojars]] 31 | :clean-targets ^{:protect false} [:target-path "resources/public/warp"] 32 | :cljsbuild {:builds {:app {:source-paths ["src/warp/client"] 33 | :compiler {:output-to "resources/public/warp/app.js" 34 | :output-dir "resources/public/warp" 35 | :asset-path "/warp" 36 | :optimizations :whitespace 37 | :pretty-print false}}}} 38 | :profiles {:dev {:cljsbuild {:builds {:app {:compiler {}}}}} 39 | :uberjar {:omit-source true 40 | :aot :all 41 | :prep-tasks ["compile" ["cljsbuild" "once"]] 42 | :cljsbuild {:jar true 43 | :builds {:app {:compiler {:optimizations :advanced 44 | :pretty-print false}}}}}}) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | warp: distributed workflow management 2 | ===================================== 3 | 4 | warp distributes scenarios across any number of machines. 5 | 6 | > hubot: deploy to production, engage! 7 | 8 | ![build status](https://travis-ci.org/pyr/warp.svg) 9 | 10 | ## The story 11 | 12 | Your applications span a large group of hosts and deployment 13 | involves several separate steps. 14 | 15 | Let's say that you have a web application deployment process 16 | that involves: 17 | 18 | - Updating a git repository 19 | - Restarting a service 20 | 21 | This process is likely the same for several profiles such as 22 | *test*, *staging* and *production*. 23 | 24 | ![warp scenarios](http://i.imgur.com/6svdQH9.png) 25 | 26 | Warp provides a DSL for writing scenarios and schedules 27 | executions over a pub-sub system, streaming the results 28 | to the controller which makes results available through 29 | an API and web view 30 | 31 | Command executions can be scheduled through the following means: 32 | 33 | - The web interface exposed by the controller 34 | - API queries to the controller 35 | - IRC/Campfire/Hipchat through a [hubot](http://hubot.github.io) script 36 | 37 | ## A sample scenario 38 | 39 | ```clj 40 | {:timeout 2 41 | :name "ping" 42 | :matcher {:type :none} 43 | :profiles {:everyone {:type :all} 44 | :platform {:type :and :clauses [{:type :fact :fact "facter.sp_environment" :value "{{0}}"} 45 | {:type :fact :fact "facter.platform" :value "{{1}}"}]} 46 | :prod {:type :fact :fact "facter.sp_environment" :value "prod"} 47 | :preprod {:type :fact :fact "facter.sp_environment" :value "preprod"} 48 | :host {:type :host :host "{{0}}"}} 49 | :commands [{:type :ping}]} 50 | ``` 51 | 52 | ## More screenshots 53 | 54 | ![warp index](http://i.imgur.com/qawWTTX.png) 55 | ![warp output](http://i.imgur.com/sYVRCHf.png) 56 | 57 | ## Pub-Sub support 58 | 59 | Scenario execution happens through an HTTP api. 60 | 61 | Warp and [warp-agent](https://github.com/pyr/warp-agent) borrow 62 | from [mcollective](http://puppetlabs.com/mcollective) and my first 63 | implementation within [amiral](https://github.com/pyr/amiral) 64 | 65 | Warp aims to improve on amiral in the following ways: 66 | 67 | - Bundle matchers, timeouts and a list of commands (a script) options 68 | in named "scenarios" 69 | - Extract out of the IRC bot framework and display extended execution 70 | results in a web view 71 | - Deprecate signing requests with ssh-keys and move to SSL 72 | - Provide a lighter-weight agent 73 | - Support arguments 74 | 75 | ## Development 76 | 77 | To build and run the controller: 78 | 79 | lein cljsbuild 80 | lein run -- -f doc/controller.clj 81 | 82 | When working on the ClojureScript part, you can automatically rebuild 83 | it when a change happens: 84 | 85 | lein cljsbuild auto 86 | 87 | You can run the agent with: 88 | 89 | ./agent/warp-agent doc/warp-agent.json 90 | -------------------------------------------------------------------------------- /agent/src/warp/request.go: -------------------------------------------------------------------------------- 1 | package warp 2 | 3 | type PacketOpcode string 4 | 5 | const ( 6 | OPCODE_PING PacketOpcode = "ping" 7 | OPCODE_SCRIPT PacketOpcode = "script" 8 | OPCODE_COMMAND_DENY PacketOpcode = "command-denied" 9 | OPCODE_COMMAND_START PacketOpcode = "command-start" 10 | OPCODE_COMMAND_END PacketOpcode = "command-end" 11 | OPCODE_COMMAND_STEP PacketOpcode = "command-step" 12 | OPCODE_TOPOLOGY PacketOpcode = "topology" 13 | ) 14 | 15 | type PacketStatus string 16 | 17 | type Scenario struct { 18 | Name string `json:"name" binding:"required"` 19 | Timeout int `json:"timeout,omitempty"` 20 | Matcher MatcherDescription `json:"matcher" binding:"required"` 21 | Commands []CommandDescription `json:"commands" binding:"required"` 22 | } 23 | 24 | type Packet struct { 25 | Opcode PacketOpcode `json:"opcode" binding:"required"` 26 | Sequence string `json:"sequence" binding:"required"` 27 | Message string `json:"message,omitempty"` 28 | Topology map[string]string `json:"topology,omitempty"` 29 | Scenario *Scenario `json:"scenario,omitempty"` 30 | Step int `json:"step"` 31 | Host string `json:"host,omitempty"` 32 | StepOutput *CommandOutput `json:"output,omitempty"` 33 | } 34 | 35 | func (client *Client) HandleRequest(p *Packet, env Environment) { 36 | 37 | if p.Opcode == "ping" { 38 | client.Logger.Printf("received ping") 39 | client.SendPacket(Packet{ 40 | Opcode: "pong", 41 | Sequence: p.Sequence, 42 | }) 43 | return 44 | } 45 | if p.Opcode != "script" { 46 | client.Logger.Printf("invalid packet") 47 | return 48 | } 49 | 50 | // If we got this far, we have a script to run, make sure 51 | // our matcher agrees we should perform this command. 52 | matcher := p.Scenario.Matcher.DescriptionToMatcher() 53 | if !matcher.Validate(env) { 54 | client.Logger.Printf("received script, denying execution") 55 | client.SendPacket(Packet{ 56 | Opcode: "command-deny", 57 | Sequence: p.Sequence, 58 | Message: "not a valid target for matcher.", 59 | }) 60 | return 61 | } 62 | 63 | client.Logger.Printf("received script, starting.") 64 | client.SendPacket(Packet{ 65 | Opcode: "command-start", 66 | Sequence: p.Sequence, 67 | Message: "starting execution.", 68 | }) 69 | 70 | // If we got this far, we can run the script. 71 | for i, cmddesc := range p.Scenario.Commands { 72 | cmd := cmddesc.DescriptionToCommand() 73 | cmdout := cmd.Execute() 74 | client.Logger.Printf("sending step %v output.", i) 75 | client.SendPacket(Packet{ 76 | Opcode: "command-step", 77 | Sequence: p.Sequence, 78 | Step: i, 79 | StepOutput: &cmdout, 80 | }) 81 | if !cmdout.Success { 82 | break 83 | } 84 | } 85 | client.Logger.Printf("script finished.") 86 | client.SendPacket(Packet{ 87 | Opcode: "command-end", 88 | Sequence: p.Sequence, 89 | Message: "execution finished.", 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /hubot/warp.coffee: -------------------------------------------------------------------------------- 1 | # Description: 2 | # Interacts with warp: the parallel execution commander 3 | # 4 | # Commands: 5 | # hubot warp me - Schedules scenario for execution by warp 6 | # hubot engage! - Schedules scenario for execution by warp 7 | # 8 | # Configuration: 9 | # HUBOT_WARP_URL - contains warp url 10 | # HUBOT_WARP_SHOW_URL - if necessary, a different url for display purposes 11 | # 12 | 13 | EventSource = require 'eventsource' 14 | 15 | class Warp 16 | acks: 0 17 | ack_starting: 0 18 | acks_done: false 19 | done: 0 20 | 21 | constructor: (@scenario, @client) -> 22 | history = (process.env.HUBOT_WARP_SHOW_URL or process.env.HUBOT_WARP_URL) + '#/scenarios/' + @scenario 23 | @client.send "executing " + scenario + ", waiting 2 seconds for acks, reporting to: " + history 24 | 25 | process: (msg) -> 26 | 27 | if ((msg.type == 'resp' || msg.type == 'stop') && ! @acks_done) 28 | @acks_done = true 29 | @client.send @scenario + ": got " + @ack_starting + "/" + @acks + " positive acknowledgements" 30 | 31 | if (msg.type == 'ack') 32 | @acks++ 33 | if (msg.msg.status == 'starting') 34 | @ack_starting++ 35 | 36 | if (msg.type == 'resp') 37 | if (msg.msg.output.status == 'finished') 38 | @done++ 39 | @client.send @scenario + ': ' + msg.msg.host + ': success (' + @done + '/' + @ack_starting + ')' 40 | if (msg.msg.output.status == 'failure') 41 | @done++ 42 | @client.send @scenario + ': ' + msg.msg.host + ': failure! (' + @done + '/' + @ack_starting + ')' 43 | 44 | if (@done >= @ack_starting) 45 | @client.send @scenario + ": all done!" 46 | 47 | 48 | 49 | module.exports = (robot) -> 50 | 51 | warp_url = process.env.HUBOT_WARP_URL 52 | 53 | response = (msg, scenario, profile, margs, pargs) -> 54 | 55 | scenario = scenario 56 | .split(/\ +/) 57 | .filter((a) -> a) 58 | .join("-") 59 | args = [] 60 | if profile 61 | args.push('profile=' + encodeURIComponent(profile)) 62 | 63 | if margs 64 | args.push('matchargs=' + encodeURIComponent(arg)) for arg in margs.split(" ") 65 | 66 | if pargs 67 | args.push('args=' + encodeURIComponent(arg)) for arg in pargs.split(" ") 68 | 69 | warp = new Warp(scenario, msg) 70 | url = warp_url + "/scenarios/" + scenario + "/executions" 71 | if args.length > 0 72 | url += '?' + args.join("&") 73 | console.log url 74 | 75 | es = new EventSource(url) 76 | es.onmessage = (e) -> 77 | warp.process JSON.parse(e.data) 78 | es.onerror = -> 79 | es.close() 80 | 81 | handle = (msg) -> 82 | mo = msg.match[1].match /(.+?)(?: to (\S+)(?: ([\S ]+?))?)?(?: with (.*))$/i 83 | if mo 84 | return response msg, mo[1], mo[2], mo[3], mo[4] 85 | mo = msg.match[1].match /(.+?)(?: to (\S+)(?: ([\S ]+?))?)?$/i 86 | if mo 87 | return response msg, mo[1], mo[2], mo[3] 88 | 89 | robot.respond /(?:warp me|engage ?!) (.*)/i, handle 90 | robot.hear /(.*)[,\.] [eE]ngage ?!$/i, handle 91 | -------------------------------------------------------------------------------- /src/warp/parser.clj: -------------------------------------------------------------------------------- 1 | (ns warp.parser 2 | (:require [clojure.string :as str] 3 | [clojure.spec.alpha :as s] 4 | [clojure.edn :as edn] 5 | [warp.dsl :as dsl])) 6 | 7 | (defn get-extension 8 | [path] 9 | (when-let [[_ extension] (re-matches #"^.*\.([a-zA-Z]+)*$" path)] 10 | (let [kw (keyword (str/lower-case extension)) 11 | shortcuts {:clojure :edn :clj :edn :wrp :warp}] 12 | (shortcuts kw kw)))) 13 | 14 | (defn load-error 15 | [path] 16 | (throw (ex-info "unknown extension for path" {:path path}))) 17 | 18 | (defmulti load-config get-extension) 19 | (defmethod load-config :edn [in] (edn/read-string (slurp in))) 20 | (defmethod load-config :warp [in] (dsl/load-scenario in)) 21 | (defmethod load-config :default [in] (load-error in)) 22 | 23 | (defn validate! 24 | [input] 25 | (let [conformed (s/conform ::scenario input)] 26 | (when (= ::s/invalid conformed) 27 | (throw (ex-info "invalid input" (s/explain-data ::scenario input)))) 28 | input)) 29 | 30 | (defn load-unit 31 | [path] 32 | (validate! (load-config (str path)))) 33 | 34 | (def known-action? #{"stop" "start" "status" "restart" "reload"}) 35 | 36 | (s/def ::name string?) 37 | (s/def ::timeout pos-int?) 38 | 39 | (s/def ::clauses (s/coll-of ::matcher)) 40 | (s/def ::host string?) 41 | (s/def ::fact (s/or :string string? :keyword keyword?)) 42 | (s/def ::value string?) 43 | 44 | (defmulti matcher-type :type) 45 | (defmethod matcher-type :all [_] map?) 46 | (defmethod matcher-type :none [_] map?) 47 | (defmethod matcher-type :not [_] (s/keys :req-un [::clause])) 48 | (defmethod matcher-type :and [_] (s/keys :req-un [::clauses])) 49 | (defmethod matcher-type :or [_] (s/keys :req-un [::clauses])) 50 | (defmethod matcher-type :host [_] (s/keys :req-un [::host])) 51 | (defmethod matcher-type :fact [_] (s/keys :req-un [::fact ::value])) 52 | 53 | (s/def :matcher/type #{:all :none :not :or :and :fact :host}) 54 | (s/def ::matcher (s/multi-spec matcher-type :matcher/type)) 55 | (s/def ::clause ::matcher) 56 | 57 | (s/def ::service string?) 58 | (s/def ::interpol-val (s/and string? (partial re-matches #"\{\{[0-9*]\}\}"))) 59 | (s/def ::known-action (s/and string? known-action?)) 60 | (s/def ::action (s/or :known ::known-action :interpol ::interpol-val)) 61 | (s/def ::seconds pos-int?) 62 | 63 | (s/def ::shell string?) 64 | (s/def ::exits (s/coll-of int?)) 65 | (s/def ::cwd string?) 66 | 67 | (defmulti cmd-type :type) 68 | (defmethod cmd-type :shell [_] (s/keys :req-un [::shell] :opt-un [::exits ::cwd])) 69 | (defmethod cmd-type :ping [_] map?) 70 | (defmethod cmd-type :sleep [_] (s/keys :req-un [::seconds])) 71 | (defmethod cmd-type :service [_] (s/keys :req-un [::service ::action])) 72 | 73 | (s/def :cmd/type #{:shell :service :sleep :ping}) 74 | 75 | (s/def ::profiles (s/map-of keyword? ::matcher)) 76 | 77 | (s/def ::command (s/multi-spec cmd-type :cmd/type)) 78 | (s/def ::commands (s/coll-of ::command)) 79 | (s/def ::scenario (s/keys :req-un [::name ::matcher ::commands] 80 | :opt-un [::timeout ::profiles])) 81 | -------------------------------------------------------------------------------- /src/warp/scenario.clj: -------------------------------------------------------------------------------- 1 | (ns warp.scenario 2 | (:require [clojure.string :as str] 3 | [warp.watcher :as watcher] 4 | [clojure.tools.logging :refer [info]])) 5 | 6 | (defn ensure-sequential 7 | [x] 8 | (if (or (nil? x) (sequential? x)) x [x])) 9 | 10 | (defn interpol 11 | ([input args not-found] 12 | (when (string? input) 13 | (let [clean #(str/replace % #"(\{\{|\}\})" "") 14 | args (assoc (into {} (map-indexed #(vector (str %1) %2) 15 | (ensure-sequential args))) 16 | "*" (str/join " " args)) 17 | extract (fn [k] (or (get args (clean k)) 18 | (if (fn? not-found) 19 | (not-found k) 20 | not-found)))] 21 | (str/replace input #"\{\{[0-9*]+\}\}" extract)))) 22 | ([input args] 23 | (interpol input args ""))) 24 | 25 | (defn prepare-command 26 | [{:keys [literal type] :as cmd} args] 27 | (if (nil? cmd) 28 | (throw (ex-info "invalid command for scenario" {})) 29 | (case type 30 | :ping cmd 31 | :sleep cmd 32 | :service (if literal 33 | cmd 34 | (-> cmd 35 | (update :service interpol args "NOSERVICE") 36 | (update :action interpol args "status"))) 37 | :shell (if literal 38 | cmd 39 | (-> cmd 40 | (update :shell interpol args) 41 | (update :cwd interpol args))) 42 | (throw (ex-info "invalid command type" {:command cmd}))))) 43 | 44 | (defn prepare-matcher 45 | [{:keys [type] :as matcher} args] 46 | (if (nil? matcher) 47 | {:type "none"} 48 | (case type 49 | :all matcher 50 | :none matcher 51 | :host (update matcher :host #(interpol % args "")) 52 | :fact (update matcher :value #(interpol % args "")) 53 | :not (update matcher :clause #(prepare-matcher % args)) 54 | :or (update matcher :clauses #(mapv prepare-matcher % (repeat args))) 55 | :and (update matcher :clauses #(mapv prepare-matcher % (repeat args))) 56 | (throw (ex-info "invalid matcher" {:matcher matcher :status 400}))))) 57 | 58 | (defn prepare-raw 59 | [scenario profile matchargs args] 60 | (let [profiles (:profiles scenario)] 61 | (-> scenario 62 | (cond-> profile (assoc :matcher 63 | (or (get profiles (keyword profile)) 64 | (throw (ex-info "invalid profile" {}))))) 65 | (update :matcher #(prepare-matcher % matchargs)) 66 | (update :commands #(map prepare-command % (repeat args))) 67 | (update :commands vec) 68 | (dissoc :profiles)))) 69 | 70 | (defn fetch 71 | [{:keys [watcher]} id {:keys [profile matchargs args]}] 72 | (info "preparing for: " (pr-str {:id id 73 | :profile profile 74 | :matchargs matchargs 75 | :args args})) 76 | (let [scenario (prepare-raw (watcher/by-id watcher id) profile matchargs args)] 77 | (info "got scenario: " (pr-str scenario)) 78 | scenario)) 79 | -------------------------------------------------------------------------------- /agent/src/warp/matcher.go: -------------------------------------------------------------------------------- 1 | package warp 2 | 3 | type Clause interface { 4 | Validate(Environment) bool 5 | } 6 | 7 | type MatcherType string 8 | 9 | const ( 10 | MATCHER_ALL string = "all" 11 | MATCHER_NONE string = "none" 12 | MATCHER_NOT string = "not" 13 | MATCHER_OR string = "or" 14 | MATCHER_AND string = "and" 15 | MATCHER_HOST string = "host" 16 | MATCHER_FACT string = "fact" 17 | ) 18 | 19 | type MatcherDescription struct { 20 | Type MatcherType `json:"type" binding:"required"` 21 | Clause *MatcherDescription `json:"clause,omitempty"` 22 | Clauses []*MatcherDescription `json:"clauses,omitempty"` 23 | FactKey string `json:"fact,omitempty"` 24 | FactValue string `json:"value,omitempty"` 25 | Host string `json:"host,omitempty"` 26 | } 27 | 28 | type AllMatcher struct { 29 | } 30 | 31 | type NoneMatcher struct { 32 | } 33 | 34 | type AndMatcher struct { 35 | Clauses []Clause 36 | } 37 | 38 | type OrMatcher struct { 39 | Clauses []Clause 40 | } 41 | 42 | type NotMatcher struct { 43 | NotClause Clause 44 | } 45 | 46 | type HostMatcher struct { 47 | Host string 48 | } 49 | 50 | type FactMatcher struct { 51 | FactKey string 52 | FactValue string 53 | } 54 | 55 | func (m AllMatcher) Validate(env Environment) bool { 56 | return true 57 | } 58 | 59 | func (m NoneMatcher) Validate(env Environment) bool { 60 | return false 61 | } 62 | 63 | func (m AndMatcher) Validate(env Environment) bool { 64 | 65 | for c := range m.Clauses { 66 | if !m.Clauses[c].Validate(env) { 67 | return false 68 | } 69 | } 70 | return true 71 | } 72 | 73 | func (m OrMatcher) Validate(env Environment) bool { 74 | for c := range m.Clauses { 75 | if m.Clauses[c].Validate(env) { 76 | return true 77 | } 78 | } 79 | return false 80 | } 81 | 82 | func (m NotMatcher) Validate(env Environment) bool { 83 | return !m.NotClause.Validate(env) 84 | } 85 | 86 | func (m HostMatcher) Validate(env Environment) bool { 87 | return (env.Host() == m.Host) 88 | } 89 | 90 | func (m FactMatcher) Validate(env Environment) bool { 91 | if env.Lookup(m.FactKey) == m.FactValue { 92 | return true 93 | } 94 | return false 95 | 96 | } 97 | 98 | func (md MatcherDescription) DescriptionToMatcher() Clause { 99 | switch { 100 | case md.Type == "all": 101 | return AllMatcher{} 102 | case md.Type == "none": 103 | return NoneMatcher{} 104 | case md.Type == "host": 105 | return HostMatcher{Host: md.Host} 106 | case md.Type == "fact": 107 | return FactMatcher{FactKey: md.FactKey, FactValue: md.FactValue} 108 | case md.Type == "not": 109 | return NotMatcher{NotClause: md.Clause.DescriptionToMatcher()} 110 | case md.Type == "or": 111 | clauses := make([]Clause, 0) 112 | for _, desc := range md.Clauses { 113 | clauses = append(clauses, desc.DescriptionToMatcher()) 114 | } 115 | return OrMatcher{Clauses: clauses} 116 | case md.Type == "and": 117 | clauses := make([]Clause, 0) 118 | for _, desc := range md.Clauses { 119 | clauses = append(clauses, desc.DescriptionToMatcher()) 120 | } 121 | return AndMatcher{Clauses: clauses} 122 | } 123 | return NoneMatcher{} 124 | } 125 | -------------------------------------------------------------------------------- /src/warp/transport.clj: -------------------------------------------------------------------------------- 1 | (ns warp.transport 2 | " 3 | A TCP server for warp. Funnels all client messages in a single 4 | input stream. Clients are subscribed to an event bus which is 5 | used to broadcast messages. 6 | " 7 | (:require [com.stuartsierra.component :as component] 8 | [cheshire.core :as json] 9 | [manifold.stream :as stream] 10 | [manifold.bus :as bus] 11 | [manifold.deferred :as d] 12 | [aleph.tcp :as tcp] 13 | [gloss.core :as gloss] 14 | [gloss.io :as io] 15 | [warp.ssl :as ssl] 16 | [warp.pipeline :as pipeline] 17 | [clojure.tools.logging :refer [info warn error]])) 18 | 19 | (defn encode 20 | [x] 21 | (try 22 | (json/generate-string x) 23 | (catch Exception e 24 | (error e "JSON encode")))) 25 | 26 | (defn decode 27 | [x] 28 | (try 29 | (json/parse-string (String. x "UTF-8") true) 30 | (catch Exception e 31 | (error e "JSON decode")))) 32 | 33 | (defn make-handler 34 | [bus mux] 35 | (let [decoder (comp (map decode) (remove nil?))] 36 | (fn [duplex info] 37 | (d/future 38 | (try 39 | (warn "incoming client" (:remote-addr info)) 40 | (stream/on-closed duplex #(warn "lost client" (:remote-addr info))) 41 | (stream/connect (stream/transform decoder 10 duplex) 42 | mux 43 | {:downstream? false}) 44 | (stream/connect (bus/subscribe bus :out) duplex) 45 | (catch Exception e 46 | (error e "warp/tcp handler error"))))))) 47 | 48 | (defn make-pipeline 49 | [initial-pipeline] 50 | (-> initial-pipeline 51 | (.addBefore "handler" "timeout" (pipeline/read-timeout 30)) 52 | (.addBefore "timeout" "length-in" (pipeline/length-decoder)) 53 | (.addBefore "length-in" "length-out" (pipeline/length-encoder)))) 54 | 55 | (defrecord Transport [host port ssl server group mux clients] 56 | component/Lifecycle 57 | (start [this] 58 | (let [encoder (comp (map encode) (remove nil?)) 59 | bus (bus/event-bus #(stream/stream* {:buffer-size 10 60 | :xform encoder})) 61 | options (cond-> {:host (or host "localhost") 62 | :port (or port 1337) 63 | :epoll? true 64 | :pipeline-transform make-pipeline} 65 | (some? ssl) 66 | (assoc :ssl-context (ssl/server-context ssl))) 67 | server (tcp/start-server (make-handler bus mux) options)] 68 | (assoc this :bus bus :server server))) 69 | (stop [this] 70 | (when (some? server) 71 | (.close server)) 72 | (assoc this :bus nil :server nil))) 73 | 74 | (defn broadcast 75 | [{:keys [bus]} msg] 76 | (bus/publish! bus :out msg)) 77 | 78 | (defn make-mux 79 | [] 80 | (stream/stream* {:buffer-size 2048 81 | :permanent? true})) 82 | 83 | (defn make-transport 84 | [options] 85 | (map->Transport options)) 86 | -------------------------------------------------------------------------------- /agent/src/warp/command.go: -------------------------------------------------------------------------------- 1 | package warp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | type CommandType string 14 | 15 | const ( 16 | COMMAND_PING CommandType = "ping" 17 | COMMAND_SLEEP CommandType = "sleep" 18 | COMMAND_SERVICE CommandType = "service" 19 | COMMAND_SHELL CommandType = "shell" 20 | ) 21 | 22 | type CommandDescription struct { 23 | Type CommandType `json:"type" binding:"required"` 24 | Seconds time.Duration `json:"seconds,omitempty"` 25 | Service *string `json:"service,omitempty"` 26 | Action *string `json:"action,omitempty"` 27 | Cwd *string `json:"cwd,omitempty"` 28 | Exits *[]int `json:"exits,omitempty"` 29 | ShellScript string `json:"shell,omitempty"` 30 | } 31 | 32 | type CommandOutput struct { 33 | Success bool `json:"success"` 34 | ExitCode int `json:"exit"` 35 | Output string `json:"output"` 36 | } 37 | 38 | type Command interface { 39 | Execute() CommandOutput 40 | } 41 | 42 | type PingCommand struct { 43 | } 44 | 45 | type SleepCommand struct { 46 | Seconds time.Duration 47 | } 48 | 49 | type ServiceCommand struct { 50 | Service string 51 | Action string 52 | } 53 | 54 | type ShellCommand struct { 55 | Cwd string 56 | Exits *[]int 57 | ShellScript string 58 | } 59 | 60 | func (pc PingCommand) Execute() CommandOutput { 61 | return CommandOutput{Success: true, ExitCode: 0, Output: "Alive, thanks."} 62 | } 63 | 64 | func (sc SleepCommand) Execute() CommandOutput { 65 | time.Sleep(sc.Seconds * time.Second) 66 | return CommandOutput{Success: true, ExitCode: 0, Output: "Yawn."} 67 | } 68 | 69 | func ValidExit(exits *[]int, exitCode int) bool { 70 | for _, e := range *exits { 71 | if e == exitCode { 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | func (sh ShellCommand) Execute() CommandOutput { 79 | oldwd, err := os.Getwd() 80 | if err != nil { 81 | return CommandOutput{ 82 | Success: false, 83 | ExitCode: -1, 84 | Output: fmt.Sprintf("cannot get dir: %v", err), 85 | } 86 | } 87 | stdin := strings.NewReader("") 88 | var out bytes.Buffer 89 | 90 | if err = os.Chdir(sh.Cwd); err != nil { 91 | return CommandOutput{ 92 | Success: false, 93 | ExitCode: -1, 94 | Output: fmt.Sprintf("cannot change dir: %v", err), 95 | } 96 | } 97 | cmd := exec.Command("bash", "-c", sh.ShellScript) 98 | cmd.Stdin = stdin 99 | cmd.Stdout = &out 100 | cmd.Stderr = &out 101 | err = cmd.Run() 102 | exitCode := 0 103 | output := "" 104 | if err != nil { 105 | if exitError, ok := err.(*exec.ExitError); ok { 106 | waitStatus := exitError.Sys().(syscall.WaitStatus) 107 | exitCode = waitStatus.ExitStatus() 108 | output = out.String() 109 | } else { 110 | exitCode = -1 111 | output = fmt.Sprintf("cannot run process: %v", err) 112 | } 113 | } else { 114 | output = out.String() 115 | 116 | } 117 | os.Chdir(oldwd) 118 | return CommandOutput{Success: ValidExit(sh.Exits, exitCode), ExitCode: exitCode, Output: output} 119 | } 120 | 121 | func (sc ServiceCommand) Execute() CommandOutput { 122 | stdin := strings.NewReader("") 123 | 124 | var out bytes.Buffer 125 | 126 | cmd := exec.Command("service", sc.Service, sc.Action) 127 | cmd.Stdin = stdin 128 | cmd.Stdout = &out 129 | cmd.Stderr = &out 130 | 131 | err := cmd.Run() 132 | 133 | exitCode := 0 134 | output := "" 135 | if err != nil { 136 | if exitError, ok := err.(*exec.ExitError); ok { 137 | waitStatus := exitError.Sys().(syscall.WaitStatus) 138 | exitCode = waitStatus.ExitStatus() 139 | output = out.String() 140 | } else { 141 | exitCode = -1 142 | output = fmt.Sprintf("cannot run process: %v", err) 143 | } 144 | } else { 145 | output = out.String() 146 | } 147 | return CommandOutput{Success: (exitCode == 0), ExitCode: exitCode, Output: output} 148 | } 149 | 150 | func (cd CommandDescription) DescriptionToCommand() Command { 151 | switch { 152 | case cd.Type == "ping": 153 | return PingCommand{} 154 | case cd.Type == "sleep": 155 | return SleepCommand{Seconds: cd.Seconds} 156 | case cd.Type == "service": 157 | return ServiceCommand{Service: *cd.Service, Action: *cd.Action} 158 | case cd.Type == "shell": 159 | cwd := "/" 160 | exits := &[]int{0} 161 | if cd.Cwd != nil { 162 | cwd = *cd.Cwd 163 | } 164 | if cd.Exits != nil { 165 | exits = cd.Exits 166 | } 167 | return ShellCommand{Cwd: cwd, Exits: exits, ShellScript: cd.ShellScript} 168 | } 169 | return PingCommand{} 170 | } 171 | -------------------------------------------------------------------------------- /hubot/warp2.coffee: -------------------------------------------------------------------------------- 1 | # Description: 2 | # Interacts with warp: the parallel execution commander 3 | # 4 | # Commands: 5 | # hubot , 🚀 - Schedules scenario for execution by warp 6 | # hubot , rocket! - Schedules scenario for execution by warp 7 | # 8 | # Configuration: 9 | # HUBOT_WARP2_URL - contains warp url 10 | # HUBOT_WARP2_SHOW_URL - if necessary, a different url for display purposes 11 | # 12 | 13 | EventSource = require 'eventsource' 14 | c = require('irc-colors') 15 | 16 | class WarpHost 17 | reported: 0 18 | total: 0 19 | success: true 20 | finished: false 21 | constructor: (@host, @scenario, @client, @index) -> 22 | @finished = false 23 | step: (success) -> 24 | @success = success 25 | done: (should_show, index) -> 26 | @finished = true 27 | if should_show 28 | if @success 29 | @client.send @scenario + ': ' + c.bold(@host) + ': ' + c.green('success') + ' (' + @index + '/' + @total + ')' 30 | else 31 | @client.send @scenario + ': ' + c.bold(@host) + ': ' + c.red.bold('failure!') + ' (' + @index + '/' + @total + ')' 32 | ack_timeout: (total) -> 33 | @total = total 34 | if @finished 35 | @done true 36 | timeout: () -> 37 | @client.send @scenario + ': ' + c.bold(@host) + ': ' + c.red.bold('timed out!') + ' (' + @index + '/' + @total + ')' 38 | 39 | 40 | class Warp2 41 | running: 0 42 | acked: 0 43 | success: 0 44 | done: 0 45 | statuses: undefined 46 | ack_timeout: 0 47 | 48 | constructor: (@scenario, @client) -> 49 | @statuses = new Object() 50 | @client.send "executing " + c.blue(scenario) 51 | 52 | process: (msg) -> 53 | 54 | if msg.type == 'event' && msg.event.opcode == 'init' 55 | history = (process.env.HUBOT_WARP2_SHOW_URL or process.env.HUBOT_WARP2_URL) + '#/replay/' + msg.event.sequence 56 | @client.send @scenario + ": reporting to: " + c.blue(history) 57 | else if msg.type == 'event' && msg.event.opcode == 'ack-timeout' 58 | @ack_timeout = 1 59 | @client.send @scenario + ": got " + @running + "/" + @acked + " positive acks" 60 | status.ack_timeout(@running) for host,status of @statuses 61 | else if msg.type == 'event' && msg.event.opcode == 'timeout' 62 | @client.send "scenario timeout reached" 63 | else if msg.type == 'state' 64 | if msg.state == 'closed' 65 | status.timeout for host,status of @statuses 66 | @client.send @scenario + ": all done!" 67 | else if msg.type == 'event' && msg.event.opcode == 'command-start' 68 | @running++ 69 | @acked++ 70 | @statuses[msg.event.host] = new WarpHost(msg.event.host, @scenario, @client, @running) 71 | else if msg.type == 'event' && msg.event.opcode == 'command-deny' 72 | @acked++ 73 | else if msg.type == 'event' && msg.event.opcode == 'command-end' 74 | @done++ 75 | @statuses[msg.event.host].done(@ack_timeout) 76 | else if msg.type == 'event' && msg.event.opcode == 'command-step' 77 | @done++ 78 | @statuses[msg.event.host].step(msg.event.output.success) 79 | else 80 | @client.send @scenario + ": unknown payload: " + msg 81 | 82 | module.exports = (robot) -> 83 | 84 | warp_url = process.env.HUBOT_WARP2_URL 85 | 86 | response = (msg, scenario, profile, margs, pargs) -> 87 | 88 | scenario = scenario 89 | .split(/\ +/) 90 | .filter((a) -> a) 91 | .join("-") 92 | args = [] 93 | if profile 94 | args.push('profile=' + encodeURIComponent(profile)) 95 | 96 | if margs 97 | args.push('matchargs=' + encodeURIComponent(arg)) for arg in margs.split(" ") 98 | 99 | if pargs 100 | args.push('args=' + encodeURIComponent(arg)) for arg in pargs.split(" ") 101 | scenario = scenario 102 | .split(/\ +/) 103 | .filter((a) -> a) 104 | .join("-") 105 | 106 | warp = new Warp2(scenario, msg) 107 | url = warp_url + "/api/scenarios/" + scenario + "/run" 108 | if args.length > 0 109 | url += '?' + args.join("&") 110 | console.log('warp2 url: ' + url) 111 | 112 | es = new EventSource(url) 113 | es.onmessage = (e) -> 114 | warp.process JSON.parse(e.data) 115 | es.onerror = (e) -> 116 | es.close() 117 | 118 | handle = (msg) -> 119 | mo = msg.match[1].match /(.+?)(?: to (\S+)(?: ([\S ]+?))?)?(?: with (.*))$/i 120 | if mo 121 | return response msg, mo[1], mo[2], mo[3], mo[4] 122 | mo = msg.match[1].match /(.+?)(?: to (\S+)(?: ([\S ]+?))?)?$/i 123 | if mo 124 | return response msg, mo[1], mo[2], mo[3] 125 | 126 | robot.hear /(?:hubot:? *)?(.*)[,\.] [eE]ngage ?!$/i, handle 127 | robot.hear /(?:hubot:? *)?(.*)[,\.] 🚀$/i, handle 128 | -------------------------------------------------------------------------------- /src/warp/execution.clj: -------------------------------------------------------------------------------- 1 | (ns warp.execution 2 | (:require [clojure.tools.logging :as log])) 3 | 4 | 5 | (defrecord Event [host opcode sequence step output]) 6 | 7 | (defrecord Client [host state steps index max]) 8 | 9 | (defrecord Execution [id scenario state accepted refused 10 | total clients listener can-close?]) 11 | 12 | (defn make-client 13 | [host max] 14 | (map->Client {:host host 15 | :state :running 16 | :index 0 17 | :max max 18 | :steps []})) 19 | 20 | (defn make-execution 21 | [id scenario listener] 22 | (map->Execution {:id id 23 | :scenario scenario 24 | :state :running 25 | :accepted 0 26 | :refused 0 27 | :total 0 28 | :listener listener 29 | :clients {} 30 | :can-close? false})) 31 | 32 | (defn close 33 | [client] 34 | (assoc client :state :closed)) 35 | 36 | (defn sanitize-state 37 | [execution] 38 | (when (some? execution) 39 | (let [can-close? (:can-close? execution) 40 | clients (:clients execution) 41 | closed? (comp (partial = :closed) :state) 42 | open? (complement closed?)] 43 | (if (and can-close? (open? execution) (every? closed? (vals clients))) 44 | (assoc execution :state :closed) 45 | execution)))) 46 | 47 | (defmulti augment (fn [something event] (class something))) 48 | 49 | (defmethod augment Client 50 | [{:keys [state index max] :as client} {:keys [step output]}] 51 | (when-not (= step index) 52 | (throw (ex-info (str "augment: invalid step index" 53 | (pr-str {:step step :index index})) 54 | {:step step :index index}))) 55 | (when (>= step max) 56 | (throw (ex-info "augment: step index out of bounds" {}))) 57 | 58 | (-> client 59 | (update :steps conj output) 60 | (update :index inc) 61 | (cond-> (not (:success output)) close))) 62 | 63 | (defmethod augment Execution 64 | [{:keys [id scenario state] :as execution} {:keys [host opcode sequence step] :as event}] 65 | (log/debug "transition:" state "=>" opcode) 66 | (sanitize-state 67 | (let [max (count (:commands scenario))] 68 | (cond 69 | (not (= id sequence)) 70 | (do 71 | (log/error "augment: invalid event sequence") 72 | execution) 73 | 74 | (= state :close) 75 | ;; This will return nil 76 | (log/error "augment: input while execution closed" {}) 77 | 78 | (= opcode :init) 79 | execution 80 | 81 | (= opcode :ack-timeout) 82 | (assoc execution :can-close? true) 83 | 84 | (= opcode :timeout) 85 | (-> execution 86 | (update :clients (fn [cs] (zipmap (keys cs) (mapv close (vals cs))))) 87 | (assoc :state :closed)) 88 | 89 | (= opcode :command-start) 90 | (-> execution 91 | (update :accepted inc) 92 | (update :total inc) 93 | (assoc-in [:clients host] (make-client host max))) 94 | 95 | (contains? #{:command-deny :command-denied} opcode) 96 | (-> execution 97 | (update :refused inc) 98 | (update :total inc)) 99 | 100 | (= opcode :command-end) 101 | (update-in execution [:clients host] close) 102 | 103 | :command-step 104 | (if (get-in execution [:clients host]) 105 | (update-in execution [:clients host] augment event) 106 | (do (log/error "augment: invalid client in event") 107 | execution)) 108 | 109 | :else 110 | (log/error "augment: invalid event or state"))))) 111 | 112 | (defmethod augment nil 113 | [_ event] 114 | (log/info "augment: stray event: " (pr-str event)) 115 | nil) 116 | 117 | (comment 118 | (let [events [{:opcode :command-start :sequence 0 :host :a} 119 | {:opcode :command-deny :sequence 0 :host :b} 120 | {:opcode :command-start :sequence 0 :host :c} 121 | {:opcode :command-step :sequence 0 :step 0 :host :c :output {:success :false :exit-code 1 :output "nope"}} 122 | {:opcode :command-step :sequence 0 :step 0 :host :a :output {:success :true :exit-code 0 :output "yup"}} 123 | {:opcode :command-step :sequence 0 :step 1 :host :a :output {:success :true :exit-code 0 :output "yup"}} 124 | {:opcode :timeout :sequence 0} 125 | {:opcode :command-step :sequence 0 :step 2 :host :a :output {:success :true :exit-code 0 :output "yup"}}]] 126 | (reduce augment (make-execution 0 {:commands [:ping :service :shell]}) events)) 127 | ) 128 | -------------------------------------------------------------------------------- /src/warp/engine.clj: -------------------------------------------------------------------------------- 1 | (ns warp.engine 2 | (:require [com.stuartsierra.component :as com] 3 | [clojure.string :as str] 4 | [manifold.stream :as stream] 5 | [manifold.deferred :as d] 6 | [warp.archive :as arc] 7 | [warp.transport :as transport] 8 | [warp.execution :as x] 9 | [warp.watcher :as watcher] 10 | [warp.scenario :as scenario] 11 | [warp.archive :as archive] 12 | [unilog.context :refer [with-context]] 13 | [clojure.tools.logging :refer [debug info warn error]])) 14 | 15 | (defprotocol ExecutionListener 16 | (publish-state [this state]) 17 | (publish-event [this event])) 18 | 19 | (defn random-uuid 20 | [] 21 | (str (com.datastax.driver.core.utils.UUIDs/timeBased))) 22 | 23 | (def progress-command? 24 | #{:init :command-start :command-deny :command-step :command-end 25 | :command-denied 26 | :ack-timeout :timeout}) 27 | 28 | (defn process-event 29 | [archive executions {:keys [opcode sequence] :as event}] 30 | 31 | (when-not (progress-command? opcode) 32 | (throw (ex-info "invalid event received" {:event event}))) 33 | 34 | (debug "processing: " (pr-str event)) 35 | 36 | (let [world (swap! executions update sequence x/augment event) 37 | execution (get world sequence)] 38 | (when-let [listener (:listener execution)] 39 | (publish-event listener event) 40 | (publish-state listener (dissoc execution :listener))) 41 | (when (= (:state execution) :closed) 42 | (archive/record archive 43 | (get-in execution [:scenario :id]) 44 | (:id execution) 45 | execution) 46 | (swap! executions dissoc sequence)))) 47 | 48 | (defn process-events 49 | [archive executions mux] 50 | (stream/consume 51 | (fn [payload] 52 | (try 53 | (when (some? payload) 54 | (let [event (update payload :opcode (fnil keyword :none))] 55 | (when-not (= :pong (:opcode event)) 56 | (process-event archive executions event)))) 57 | (catch Exception e 58 | (error e "serious error during processing" (pr-str payload))))) 59 | mux)) 60 | 61 | (defn run-keepalive 62 | [keepalive transport] 63 | (when keepalive 64 | (future 65 | (loop [] 66 | (Thread/sleep keepalive) 67 | (try 68 | (transport/broadcast transport {:opcode "ping" :sequence (random-uuid)}) 69 | (catch Exception e 70 | (error e "broadcast error during ping"))) 71 | (recur))))) 72 | 73 | (defrecord Engine [keepalive keepalive-thread executions 74 | transport archive watcher mux] 75 | com/Lifecycle 76 | (start [this] 77 | (let [executions (atom {})] 78 | (process-events archive executions mux) 79 | (assoc this 80 | :executions executions 81 | :keepalive-thread (run-keepalive keepalive transport)))) 82 | (stop [this] 83 | (future-cancel keepalive-thread) 84 | (assoc this :executions nil :keepalive-thread nil))) 85 | 86 | (defn build-context 87 | [scenario id {:keys [profile matchargs args]}] 88 | (cond-> {:scenario (str scenario) 89 | :id (str id)} 90 | (some? profile) (assoc :profile (str profile)) 91 | (some? matchargs) (assoc :to-args (str/join "," (map str matchargs))) 92 | (some? args) (assoc :with-args (str/join "," (map str args))))) 93 | 94 | 95 | (defn new-execution 96 | [engine scenario-id listener args] 97 | (let [id (random-uuid) 98 | scenario (scenario/fetch engine scenario-id args) 99 | timeout (* 1000 (or (:timeout scenario) 5)) 100 | ack-timeout (* 1000 (or (:ack-timeout scenario) 1)) 101 | executions (:executions engine) 102 | execution (x/make-execution id scenario listener)] 103 | 104 | (with-context (build-context scenario-id id args) 105 | (info "launching execution")) 106 | 107 | (swap! executions assoc id execution) 108 | 109 | 110 | (stream/put! (:mux engine) {:opcode :init :sequence id}) 111 | 112 | (d/chain (d/future (Thread/sleep ack-timeout)) 113 | (fn [_] 114 | (stream/put! (:mux engine) 115 | {:opcode :ack-timeout :sequence id}))) 116 | 117 | (d/chain (d/future (Thread/sleep timeout)) 118 | (fn [_] 119 | (stream/put! (:mux engine) 120 | {:opcode :timeout :sequence id}))) 121 | (when listener 122 | (publish-state listener (dissoc execution :listener))) 123 | (transport/broadcast (:transport engine) 124 | {:opcode "script" 125 | :sequence id 126 | :scenario scenario}) 127 | execution)) 128 | 129 | (defn find-execution 130 | [{:keys [executions archive]} scenario id] 131 | (or (get @executions id) 132 | (arc/replay archive id) 133 | (throw (ex-info "execution not found" {})))) 134 | 135 | (defn make-engine 136 | [keepalive] 137 | (map->Engine {:keepalive (* 1000 (or keepalive 30))})) 138 | -------------------------------------------------------------------------------- /agent/src/warp/client.go: -------------------------------------------------------------------------------- 1 | package warp 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "crypto/x509" 7 | "encoding/binary" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "io/ioutil" 12 | "log" 13 | "log/syslog" 14 | "net" 15 | "os" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | type Client struct { 21 | Config Config 22 | Conn net.Conn 23 | Logger *log.Logger 24 | Connected bool 25 | Input chan *Packet 26 | Output chan *Packet 27 | lock sync.Mutex 28 | } 29 | 30 | func NewClient(cfg Config) *Client { 31 | 32 | var logger *log.Logger 33 | var err error 34 | 35 | switch { 36 | case cfg.LogTo == "stdout": 37 | logger = log.New(os.Stdout, "warp: ", 0) 38 | case cfg.LogTo == "stderr": 39 | logger = log.New(os.Stderr, "warp: ", 0) 40 | case cfg.LogTo == "syslog": 41 | logger, err = syslog.NewLogger(syslog.LOG_INFO|syslog.LOG_DAEMON, 0) 42 | if err != nil { 43 | fmt.Printf("cannot initialize logger: %v", err) 44 | os.Exit(1) 45 | } 46 | default: 47 | fmt.Printf("cannot initialize logger, bad configuration") 48 | os.Exit(1) 49 | } 50 | 51 | var tlscfg *tls.Config 52 | tlscfg = nil 53 | if (cfg.Cert != "none") { 54 | cert, err := tls.LoadX509KeyPair(cfg.Cert, cfg.PrivKey) 55 | if err != nil { 56 | logger.Fatalf("cannot load certificate pair: %v", err) 57 | } 58 | data, err := ioutil.ReadFile(cfg.CaCert) 59 | if err != nil { 60 | logger.Fatalf("cannot load cacertificate: %v", err) 61 | } 62 | capool := x509.NewCertPool() 63 | capool.AppendCertsFromPEM(data) 64 | tlscfg = &tls.Config{ 65 | Certificates: []tls.Certificate{cert}, 66 | RootCAs: capool, 67 | } 68 | tlscfg.BuildNameToCertificate() 69 | } 70 | 71 | input := make(chan *Packet, 100) 72 | output := make(chan *Packet, 100) 73 | env := NewEnvironment(cfg.Host) 74 | 75 | client := &Client{Config: cfg, Logger: logger, Connected: false, Input: input, Output: output} 76 | 77 | go BuildEnv(logger, cfg.Host, env) 78 | 79 | go func() { 80 | for { 81 | client.lock.Lock() 82 | if client.Connected == false { 83 | client.lock.Unlock() 84 | var conn net.Conn 85 | if (tlscfg == nil) { 86 | conn, err = net.Dial("tcp", cfg.Server) 87 | } else { 88 | conn, err = tls.Dial("tcp", cfg.Server, tlscfg) 89 | } 90 | if err != nil { 91 | logger.Printf("unable to connect, will retry in 5 seconds: %v", err) 92 | time.Sleep(5 * time.Second) 93 | continue 94 | } 95 | client.lock.Lock() 96 | client.Conn = conn 97 | client.Connected = true 98 | logger.Printf("connected") 99 | } 100 | conn := client.Conn 101 | conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) 102 | client.lock.Unlock() 103 | p, err := ReadPacket(logger, conn) 104 | if err != nil { 105 | logger.Printf("read error: %v", err) 106 | client.lock.Lock() 107 | client.Connected = false 108 | conn.Close() 109 | client.Conn = nil 110 | client.lock.Unlock() 111 | } else { 112 | input <- p 113 | } 114 | } 115 | }() 116 | 117 | go func() { 118 | for { 119 | p := <-input 120 | client.HandleRequest(p, env) 121 | } 122 | }() 123 | 124 | go func() { 125 | for { 126 | p := <-output 127 | p.Host = cfg.Host 128 | client.lock.Lock() 129 | if client.Connected == false { 130 | logger.Printf("not connected, dropping output packet") 131 | client.lock.Unlock() 132 | continue 133 | } 134 | conn := client.Conn 135 | client.lock.Unlock() 136 | err = WritePacket(logger, conn, p) 137 | if err != nil { 138 | logger.Printf("failed to send packet") 139 | } 140 | } 141 | }() 142 | 143 | return client 144 | } 145 | 146 | func ReadPacket(logger *log.Logger, conn net.Conn) (*Packet, error) { 147 | 148 | inbuf := make([]byte, 1024) 149 | br, err := conn.Read(inbuf) 150 | if err != nil { 151 | return nil, err 152 | } 153 | if br == 0 { 154 | return nil, errors.New("disconnected") 155 | } 156 | if br < 4 { 157 | return nil, errors.New("short read") 158 | } 159 | 160 | br = br - 4 161 | phead := inbuf[0:4] 162 | b := bytes.NewReader(phead) 163 | 164 | var pilen uint32 165 | err = binary.Read(b, binary.BigEndian, &pilen) 166 | plen := int(pilen) - 4 167 | if err != nil { 168 | conn.Close() 169 | return nil, err 170 | } 171 | 172 | pdata := inbuf[4:] 173 | for { 174 | if br >= plen { 175 | break 176 | } 177 | buf := make([]byte, (plen - br)) 178 | nr, err := conn.Read(buf) 179 | if err != nil { 180 | conn.Close() 181 | return nil, err 182 | } 183 | if nr == 0 { 184 | return nil, err 185 | } 186 | pdata = append(pdata, buf...) 187 | br = br + nr 188 | } 189 | 190 | var p Packet 191 | err = json.Unmarshal(pdata[:plen], &p) 192 | 193 | if err != nil { 194 | return nil, err 195 | 196 | } 197 | return &p, nil 198 | } 199 | 200 | func WritePacket(logger *log.Logger, conn net.Conn, p *Packet) error { 201 | 202 | jsbuf, err := json.Marshal(&p) 203 | if err != nil { 204 | return err 205 | } 206 | 207 | plen := len(jsbuf) 208 | pilen := uint32(plen) 209 | 210 | outlen := new(bytes.Buffer) 211 | binary.Write(outlen, binary.BigEndian, &pilen) 212 | 213 | jsbuf = append(outlen.Bytes(), jsbuf...) 214 | 215 | bw := 0 216 | for { 217 | if bw >= (plen + 4) { 218 | break 219 | } 220 | w, err := conn.Write(jsbuf) 221 | if err != nil { 222 | return err 223 | } 224 | bw = bw + w 225 | } 226 | return nil 227 | } 228 | 229 | func (client *Client) SendPacket(p Packet) { 230 | client.Output <- &p 231 | } 232 | 233 | func (client *Client) WaitForQuit() { 234 | var wg sync.WaitGroup 235 | wg.Add(1) 236 | wg.Wait() 237 | } 238 | -------------------------------------------------------------------------------- /src/warp/client/views.cljs: -------------------------------------------------------------------------------- 1 | (ns warp.client.views 2 | (:require [clojure.string :as str] 3 | [warp.client.models :as m] 4 | [warp.client.layout :refer [h4 h3 panel table-striped link-to 5 | code console tr]] 6 | [warp.client.utils :refer [pretty-match first-line]] 7 | [warp.client.state :refer [app]])) 8 | 9 | (defn scenario-tr 10 | [{:keys [name ack-timeout timeout matcher]}] 11 | (tr (link-to (str "#/scenario/" name) name) 12 | (str "ack: " ack-timeout ", timeout:" timeout) 13 | (pretty-match matcher))) 14 | 15 | (defn scenario-list 16 | [] 17 | (m/refresh-scenarios) 18 | (fn [] 19 | (panel 20 | "Scenarios" 21 | (table-striped 22 | ["Scenario" "Timeouts" "Default Matcher"] 23 | (for [scenario (sort-by :name (:scenarios @app))] 24 | ^{:key (:name scenario)} [scenario-tr scenario]))))) 25 | 26 | (defmulti scenario-cmd-summary :type) 27 | 28 | (defmethod scenario-cmd-summary "shell" 29 | [{:keys [shell]}] 30 | [:span 31 | [:span {:class "label label-primary"} "shell"] 32 | (code (first-line shell))]) 33 | 34 | (defmethod scenario-cmd-summary "service" 35 | [{:keys [action service]}] 36 | [:span 37 | [:span {:class "label label-primary"} "service"] 38 | (code "service" service action)]) 39 | 40 | (defmethod scenario-cmd-summary "ping" 41 | [_] 42 | [:span 43 | [:span {:class "label label-primary"} "ping"]]) 44 | 45 | (defmethod scenario-cmd-summary "sleep" 46 | [{:keys [seconds]}] 47 | [:span 48 | [:span {:class "label label-primary"} "sleep"] 49 | (code "sleep" seconds)]) 50 | 51 | (defmethod scenario-cmd-summary :default 52 | [{:keys [type]}] 53 | [:span {:class "label label-primary"} type]) 54 | 55 | (defmulti scenario-cmd :type) 56 | 57 | (defmethod scenario-cmd "shell" 58 | [{:keys [cwd exits shell literal]}] 59 | (tr "script" (console shell))) 60 | 61 | (defmethod scenario-cmd "service" 62 | [{:keys [service action]}] 63 | (tr "service" (console "service " service " " action))) 64 | 65 | (defmethod scenario-cmd "ping" 66 | [_] 67 | (tr "ping" "")) 68 | 69 | (defmethod scenario-cmd "sleep" 70 | [{:keys [seconds]}] 71 | (tr "sleep" seconds)) 72 | 73 | (defn scenario-cmd-list 74 | [cmds] 75 | (for [[i cmd] (map-indexed vector cmds)] 76 | ^{:key (str "cmd-" i)} [scenario-cmd cmd])) 77 | 78 | 79 | (defn scenario-profile 80 | [[id profile]] 81 | (tr (name id) (code (pretty-match (:matcher profile))))) 82 | 83 | (defn scenario-profile-list 84 | [profiles] 85 | (for [[i profile] (map-indexed vector profiles)] 86 | ^{:key (str "profile-" i)} [scenario-profile profile])) 87 | 88 | (defn scenario-description 89 | [{:keys [matcher profiles commands id]}] 90 | (println "building scenario description") 91 | (panel 92 | [:span (link-to "#/scenarios" "Scenarios") " / " id] 93 | [:div 94 | (h4 "Commands") 95 | (table-striped ["Type" "Description"] (scenario-cmd-list commands)) 96 | (h4 "Profiles") 97 | (table-striped ["Name" "Matcher"] (scenario-profile-list profiles))])) 98 | 99 | (defn scenario-replays 100 | [replays] 101 | (panel 102 | "Last runs" 103 | [:ul 104 | (for [[i r] (map-indexed vector replays)] 105 | ^{:key (str "replay-" i)} [:li [:a {:href (str "#/replay/" r)} r]])])) 106 | 107 | (defn scenario-detail 108 | [{:keys [id] :as params}] 109 | (m/refresh-scenario id) 110 | (fn [] 111 | (let [{:keys [scenario replays]} (get-in @app [:scenario id])] 112 | [:div 113 | [scenario-description scenario] 114 | (when (seq replays) 115 | [scenario-replays replays])]))) 116 | 117 | (defn success-output 118 | [success?] 119 | (if success? 120 | [:span {:class "label label-success"} "success"] 121 | [:span {:class "label label-danger"} "failure"])) 122 | 123 | (defn scenario-output-summary 124 | [{:keys [replay host steps]}] 125 | (apply tr (concat [(link-to (str "#/replay/" replay "/" host) host)] 126 | (for [{:keys [success index]} steps] 127 | ^{:key (str "step-" index)} 128 | (success-output success)) 129 | [(success-output (every? :success steps))]))) 130 | 131 | (defn replay-summary 132 | [{:keys [clients accepted refused total scenario id]}] 133 | (panel 134 | [:span "Run:" (code id)] 135 | (table-striped 136 | (vec 137 | (concat ["Host"] 138 | (for [[i cmd] (map-indexed vector (:commands scenario))] 139 | ^{:key (str "cmd-" i)} (scenario-cmd-summary cmd)) 140 | ["Completion"])) 141 | (for [[host client] clients] 142 | ^{:key host} [scenario-output-summary (assoc client :replay id)])))) 143 | 144 | (defn replay-detail 145 | [{:keys [id]}] 146 | (m/refresh-replay id) 147 | (fn [] 148 | (let [execution (get-in @app [:executions id])] 149 | [:div 150 | [scenario-description (:scenario execution)] 151 | [replay-summary execution]]))) 152 | 153 | (defn client-step-output 154 | [{:keys [output] :as cmd}] 155 | (println "cmd step:" (pr-str cmd)) 156 | [:div 157 | [:h4 (:type cmd) [:span {:style #js {"float" "right"}} (success-output (:success output))]] 158 | [:div 159 | [:pre {:class "console" :dangerouslySetInnerHTML #js {:__html (:output output)}}] 160 | [:p [:span {:class "text-muted"} (str "process returned: " (:exit output))]]]]) 161 | 162 | (defn client-detail 163 | [{:keys [id host]}] 164 | (m/refresh-replay id) 165 | (fn [] 166 | (let [execution (get-in @app [:executions id]) 167 | s-id (get-in execution [:scenario :id]) 168 | commands (get-in execution [:scenario :commands]) 169 | output (get-in execution [:clients (keyword host)])] 170 | (println "commands: " commands) 171 | (println "execution:" output) 172 | (panel 173 | [:span 174 | (link-to "#/scenarios" "Scenarios") 175 | " / " 176 | (link-to (str "#/scenario/" s-id) s-id) 177 | " / " 178 | (link-to (str "#/replay/" id) id) 179 | " / " 180 | host] 181 | [:div 182 | (for [[cmd [i output]] (partition 2 (interleave commands (map-indexed vector (:steps output))))] 183 | ^{:key (str "step-" i)} [client-step-output (assoc cmd :output output)])])))) 184 | -------------------------------------------------------------------------------- /src/warp/api.clj: -------------------------------------------------------------------------------- 1 | (ns warp.api 2 | (:require [com.stuartsierra.component :as com] 3 | [manifold.stream :as stream] 4 | [manifold.bus :as bus] 5 | [cheshire.core :as json] 6 | [warp.engine :as engine] 7 | [warp.archive :as archive] 8 | [warp.watcher :as watcher] 9 | [ring.middleware.keyword-params :refer [wrap-keyword-params]] 10 | [ring.middleware.params :refer [wrap-params]] 11 | [aleph.http :refer [start-server]] 12 | [bidi.bidi :refer [match-route*]] 13 | [bidi.ring :refer [resources-maybe redirect]] 14 | [unilog.context :refer [with-context]] 15 | [clojure.tools.logging :refer [info warn error]])) 16 | 17 | (def ^:dynamic *engine* nil) 18 | (def ^:dynamic *archive* nil) 19 | (def ^:dynamic *watcher* nil) 20 | 21 | (def prefix 22 | "Resource options" 23 | {:prefix "public/"}) 24 | 25 | (def api-routes 26 | ["/" [["" (redirect "index.html")] 27 | ["api/" [["scenarios" [["" {:get :list-scenarios}] 28 | [["/" :id] {:get :get-scenario}] 29 | [["/" :id "/run"] {:get :start-execution}]]] 30 | ["executions" [["" {:get :list-executions}] 31 | [["/" :id] {:get :get-execution}]]] 32 | ["replays" [["" {:get :all-replays}] 33 | [["/" :id] {:get :get-replay}]]]]] 34 | ["" (resources-maybe prefix)]]]) 35 | 36 | (defn sse-event 37 | [body e] 38 | (try 39 | (stream/put! body (format "data: %s\n\n" (json/generate-string e))) 40 | (catch Exception e 41 | (error e "could not send SSE event, dropping silently")))) 42 | 43 | (defn client-success? 44 | [{:keys [max index]}] 45 | (= index max)) 46 | 47 | (defn log-state 48 | [{:keys [id scenario accepted refused total clients]}] 49 | (let [base-context 50 | {:id (str id) 51 | :scenario (str scenario) 52 | :accepted (str accepted) 53 | :refused (str refused) 54 | :total (str total)}] 55 | (doseq [client clients 56 | :let [host (:host client) 57 | success? (client-success? client)]] 58 | (with-context (assoc base-context 59 | :host host 60 | :success (if success? "true" "false")) 61 | (info "output for" host "is" (if success? "success" "failure")))))) 62 | 63 | (defn execution-stream-listener 64 | [body] 65 | (reify engine/ExecutionListener 66 | (publish-state [this {:keys [state] :as state}] 67 | (sse-event body {:type :state :state state}) 68 | (when (= state :closed) 69 | (log-state state) 70 | (stream/put! body "\n\n") 71 | (stream/close! body))) 72 | (publish-event [this event] 73 | (sse-event body {:type :event :event event})))) 74 | 75 | (defn match-route 76 | [request] 77 | (match-route* api-routes (:uri request) request)) 78 | 79 | (defn any->vec 80 | [o] 81 | (cond 82 | (nil? o) nil 83 | (sequential? o) (vec o) 84 | :else (vector o))) 85 | 86 | (defn build-args 87 | [{:keys [params] :as request}] 88 | (let [profile (some-> params :profile keyword) 89 | matchargs (any->vec (:matchargs params)) 90 | args (any->vec (:args params))] 91 | (cond-> {} 92 | (some? profile) (assoc :profile profile) 93 | (some? matchargs) (assoc :matchargs matchargs) 94 | (some? args) (assoc :args args)))) 95 | 96 | (defmulti dispatch :handler) 97 | 98 | (defmethod dispatch :list-scenarios 99 | [request] 100 | {:status 200 101 | :headers {:content-type "application/json"} 102 | :body (json/generate-string {:scenarios (watcher/all-scenarios *watcher*)})}) 103 | 104 | (defmethod dispatch :get-scenario 105 | [{:keys [route-params]}] 106 | (let [id (:id route-params) 107 | scenario (watcher/by-id *watcher* id) 108 | replays (archive/replays *archive* id)] 109 | {:status 200 110 | :headers {:content-type "application/json"} 111 | :body (json/generate-string {:scenario scenario :replays replays})})) 112 | 113 | (defmethod dispatch :get-replay 114 | [{:keys [route-params]}] 115 | (let [id (:id route-params) 116 | replay (archive/replay *archive* id)] 117 | (info "replay: " (pr-str replay)) 118 | {:status 200 119 | :headers {:content-type "application/json"} 120 | :body (json/generate-string {:execution replay})})) 121 | 122 | (defmethod dispatch :all-replays 123 | [{:keys [route-params]}] 124 | (let [replays (archive/all-replays *archive*)] 125 | (info "fetching replays") 126 | {:status 200 127 | :headers {:content-type "application/json"} 128 | :body (json/generate-string {:replays replays})})) 129 | 130 | (defmethod dispatch :list-executions 131 | [request] 132 | {:status 200 133 | :headers {:content-type "application/json"} 134 | :body (json/generate-string {:executions @(:executions *engine*)})}) 135 | 136 | (defmethod dispatch :start-execution 137 | [request] 138 | (let [body (stream/stream 2048) 139 | listener (execution-stream-listener body) 140 | scenario (get-in request [:route-params :id]) 141 | args (build-args request)] 142 | 143 | (engine/new-execution *engine* scenario listener args) 144 | (sse-event body {:type :info :message "starting execution"}) 145 | {:status 200 146 | :headers {:content-type "text/event-stream" 147 | :x-accel-buffering "no" 148 | :cache-control "no-cache"} 149 | :body body})) 150 | 151 | 152 | (defmethod dispatch :default 153 | [request] 154 | {:status 404 155 | :headers {:content-type "application/json"} 156 | :body (json/generate-string {:message "invalid route"})}) 157 | 158 | (defn wrap-dispatch 159 | [{:keys [handler] :as match} request] 160 | (cond (fn? handler) 161 | (handler request) 162 | 163 | (satisfies? bidi.ring/Ring handler) 164 | (bidi.ring/request handler request match) 165 | 166 | :else 167 | (dispatch match))) 168 | 169 | (defn handler-fn 170 | [{:keys [archive watcher engine]}] 171 | (fn [request] 172 | (binding [*engine* engine 173 | *watcher* watcher 174 | *archive* archive] 175 | (try 176 | (-> request 177 | (match-route) 178 | (wrap-dispatch request)) 179 | (catch Exception e 180 | (let [{:keys [status message silence?]} (ex-data e)] 181 | (when-not silence? 182 | (error e "cannot process HTTP request")) 183 | {:status (or status 500) 184 | :headers {:content-type "application/json"} 185 | :body (json/generate-string 186 | {:message (or message (.getMessage e))})})))))) 187 | 188 | (defrecord Api [port options engine watcher archive server] 189 | com/Lifecycle 190 | (start [this] 191 | (let [srv (start-server (-> (handler-fn this) 192 | (wrap-keyword-params) 193 | (wrap-params)) 194 | (assoc options :port port))] 195 | (assoc this :server srv))) 196 | (stop [this] 197 | (when server 198 | (.close server)) 199 | (assoc this :server nil))) 200 | 201 | (defn make-api 202 | [config] 203 | (map->Api config)) 204 | -------------------------------------------------------------------------------- /resources/public/vendor/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-o-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#2d6ca2));background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-primary:disabled,.btn-primary[disabled]{background-color:#2d6ca2;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f3f3f3));background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:-o-linear-gradient(top,#222 0,#282828 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#222),to(#282828));background-image:linear-gradient(to bottom,#222 0,#282828 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-o-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3071a9));background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-o-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#3278b3));background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);background-repeat:repeat-x;border-color:#3278b3}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-o-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#428bca),to(#357ebd));background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /resources/public/vendor/bootstrap/css/bootstrap.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100% \9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} --------------------------------------------------------------------------------