Codebase list golang-github-bugsnag-bugsnag-go / 60d81fa0-f1b2-4ed3-b542-7ef66e3982c4/upstream/sid
Import upstream version 2.1.2 Debian Janitor 2 years ago
232 changed file(s) with 8048 addition(s) and 3684 deletion(s). Raw diff Collapse all Expand all
1818 4. See error
1919
2020 ### Environment
21 * Bugsnag version:
21 * Bugsnag Go version:
2222 * Go version:
2323 * Integration framework version:
2424 * Martini:
2525 * Negroni:
2626 * net/http:
27 * Ravel:
27 * Revel:
2828 * Other:
2929
3030 <!--
88 - steps to reproduce
99 - expected/actual behaviour
1010
11 * Bugsnag version:
11 * Bugsnag Go version:
1212 * Go version:
1313 * Integration framework version:
1414 * Martini:
2222 Please note: we cannot promise that we will fulfil all requests
2323
2424 ## Pull Requests
25 If you have made a fix and would like to raise a pull request, please read our [CONTRIBUTING.md](../CONTRIBUTING.md) file before creating the pull request.
25 If you have made a fix and would like to raise a pull request, please read our [CONTRIBUTING.md](../CONTRIBUTING.md) file before creating the pull request.
0 name: Audit bugsnag-go dependency licenses
1
2 on: [push, pull_request]
3
4 jobs:
5 license-audit:
6 runs-on: ubuntu-latest
7 defaults:
8 run:
9 working-directory: 'go/src/github.com/bugsnag/bugsnag-go/v2'
10
11 steps:
12 - uses: actions/checkout@v2
13 with:
14 path: 'go/src/github.com/bugsnag/bugsnag-go' # relative to $GITHUB_WORKSPACE
15
16 - name: set GOPATH
17 run: |
18 bash -c 'echo "GOPATH=$GITHUB_WORKSPACE/go" >> $GITHUB_ENV'
19
20 - name: Fetch decisions.yml
21 run: curl https://raw.githubusercontent.com/bugsnag/license-audit/master/config/decision_files/global.yml -o decisions.yml
22
23 - uses: actions/setup-go@v2
24 with:
25 go-version: '1.16'
26
27 - name: install dependencies
28 run: go get -v -d ./...
29
30 - name: Run License Finder
31 run: >
32 docker run -v $PWD:/scan licensefinder/license_finder /bin/bash -lc "
33 cd /scan &&
34 license_finder --decisions-file decisions.yml --enabled-package-managers=gomodules
35 "
44 jobs:
55 test:
66
7 runs-on: ubuntu-latest
7 runs-on: ${{ matrix.os }}-latest
88 defaults:
99 run:
10 working-directory: 'go/src/github.com/bugsnag/bugsnag-go' # relative to $GITHUB_WORKSPACE
10 working-directory: 'go/src/github.com/bugsnag/bugsnag-go/v2' # relative to $GITHUB_WORKSPACE
1111 strategy:
1212 fail-fast: false
1313 matrix:
14 go-version: ['1.7', '1.8', '1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15']
14 os: [ubuntu, windows]
15 go-version: ['1.11', '1.12', '1.13', '1.14', '1.15', '1.16']
1516
1617 steps:
1718 - uses: actions/checkout@v2
1819 with:
1920 path: 'go/src/github.com/bugsnag/bugsnag-go' # relative to $GITHUB_WORKSPACE
20 - name: setup go ${{ matrix.go-version }}
21 - name: set GOPATH
22 if: matrix.os == 'ubuntu'
2123 run: |
22 curl --silent --location --output gimme https://github.com/travis-ci/gimme/raw/v1.5.4/gimme
23 chmod +x ./gimme
24 eval "$(./gimme ${{ matrix.go-version }})"
24 bash -c 'echo "GOPATH=$GITHUB_WORKSPACE/go" >> $GITHUB_ENV'
2525 - name: set GOPATH
26 run: echo "GOPATH=$GITHUB_WORKSPACE/go" >> $GITHUB_ENV
26 if: matrix.os == 'windows'
27 run: |
28 bash -c 'echo "GOPATH=$GITHUB_WORKSPACE\\\\go" >> $GITHUB_ENV'
29 - uses: actions/setup-go@v2
30 with:
31 go-version: ${{ matrix.go-version }}
2732 - name: install dependencies
28 run: make alldeps
29 - name: run tests (go1.7 - go1.10)
30 # gin requires go1.11+
31 if: matrix.go-version == '1.7' || matrix.go-version == '1.8' || matrix.go-version == '1.9' || matrix.go-version == '1.10'
32 run: go test . ./martini ./negroni ./sessions ./headers ./errors
33 run: go get -v -d ./...
3334 - name: run tests
34 if: matrix.go-version != '1.7' && matrix.go-version != '1.8' && matrix.go-version != '1.9' && matrix.go-version != '1.10'
35 run: go test . ./gin ./martini ./negroni ./sessions ./headers ./errors
36 - name: vet package (go1.7 - go1.10)
37 if: matrix.go-version == '1.7' || matrix.go-version == '1.8' || matrix.go-version == '1.9' || matrix.go-version == '1.10'
38 run: go vet . ./martini ./negroni ./sessions ./headers ./errors
35 run: go test ./...
3936 - name: vet package
4037 # go1.12 vet shows spurious 'unknown identifier' issues
41 if: matrix.go-version != '1.7' && matrix.go-version != '1.8' && matrix.go-version != '1.9' && matrix.go-version != '1.10' && matrix.go-version != '1.12'
42 run: go vet . ./gin ./martini ./negroni ./sessions ./headers ./errors
38 if: matrix.go-version != '1.12'
39 run: go vet ./...
4340
4441 - name: install integration dependencies
42 if: matrix.os == 'ubuntu'
4543 run: |
4644 sudo apt-get install docker-compose
4745 sudo gem install bundler
4846 bundle install
49 - name: maze tests - plain Go apps
47 - name: maze tests
48 working-directory: go/src/github.com/bugsnag/bugsnag-go # relative to $GITHUB_WORKSPACE
49 if: matrix.os == 'ubuntu'
5050 env:
5151 GO_VERSION: ${{ matrix.go-version }}
52 run: bundle exec bugsnag-maze-runner --color --format progress features/plain_features features/net_http_features
53 - name: maze tests - Martini apps
54 env:
55 GO_VERSION: ${{ matrix.go-version }}
56 run: bundle exec bugsnag-maze-runner --color --format progress features/martini_features
57 - name: maze tests - Gin apps
58 # gin requires go1.11+
59 if: matrix.go-version != '1.7' && matrix.go-version != '1.8' && matrix.go-version != '1.9' && matrix.go-version != '1.10'
60 env:
61 GO_VERSION: ${{ matrix.go-version }}
62 run: |
63 GIN_VERSION=v1.0 bundle exec bugsnag-maze-runner --color --format progress features/gin_features
64 GIN_VERSION=v1.3.0 bundle exec bugsnag-maze-runner --color --format progress features/gin_features
65 - name: maze tests - Negroni apps
66 env:
67 GO_VERSION: ${{ matrix.go-version }}
68 run: |
69 NEGRONI_VERSION=v1.0.0 bundle exec bugsnag-maze-runner --color --format progress features/negroni_features
70 NEGRONI_VERSION=v0.3.0 bundle exec bugsnag-maze-runner --color --format progress features/negroni_features
71 # Revel flakes out periodically on go1.12-1.15 (PLAT-5295)
72 # - name: maze tests - Revel apps
73 # # Revel supports 1.12+
74 # if: matrix.go-version != '1.7' && matrix.go-version != '1.8' && matrix.go-version != '1.9' && matrix.go-version != '1.10' && matrix.go-version != '1.11'
75 # env:
76 # GO_VERSION: ${{ matrix.go-version }}
77 # run: |
78 # REVEL_VERSION=v0.21.0 REVEL_CMD_VERSION=v0.21.1 bundle exec bugsnag-maze-runner --color --format progress features/revel_features
52 run: bundle exec bugsnag-maze-runner --color --format progress
00 # Changelog
1
2 ## 2.1.2 (2021-08-24)
3
4 ### Enhancements
5
6 * Update panicwrap dependency to v1.3.4 which fixes build support for linux & darwin arm64.
7
8 ## 2.1.1 (2021-04-19)
9
10 ### Enhancements
11
12 * Update panicwrap dependency to 1.3.2, adding support for darwin arm64
13
14 ## 2.1.0 (2021-01-27)
15
16 ### Enhancements
17
18 * Support appending metadata through environment variables prefixed with
19 `BUGSNAG_METADATA_`
20
21 ### Bug fixes
22
23 * Fix `GOPATH`, `SourceRoot` and project package path stripping from stack
24 traces on Windows by using the correct path separators.
25
26 ## 2.0.0 (2021-01-18)
27
28 The v2 release adds support for Go modules, removes web framework
29 integrations from the main repository, and supports library configuration
30 through environment variables.
31
32 The new module is available via:
33
34 ```go
35 import "github.com/bugsnag/bugsnag-go/v2"
36 ```
37
38 ### Breaking Changes
39
40 * Removed `Configuration.Endpoint`. Use `Configuration.Endpoints` instead. For
41 more info and an example, see the [Upgrading guide](./UPGRADING.md)
42 * Web framework integrations have been moved to separate repositories:
43 * [bugsnag-go-gin](https://github.com/bugsnag/bugsnag-go-gin)
44 * [bugsnag-go-negroni](https://github.com/bugsnag/bugsnag-go-negroni)
45 * [bugsnag-go-revel](https://github.com/bugsnag/bugsnag-go-revel)
46 * The `martini` framework integration has been retired
47 * `bugsnag.VERSION` has been renamed `bugsnag.Version`
48
49 ### Enhancements
50
51 * Support configuring Bugsnag through environment variables
52 * Support reporting panics caused by overflowing the stack
53
54 ## 1.9.0 (2021-01-05)
55
56 ### Enhancements
57
58 * Support capturing "fatal error"-style panics from go, such as from concurrent
59 map read/writes, out of memory errors, and nil goroutines.
60
61 ## 1.8.0 (2020-12-03)
62
63 ### Enhancements
64
65 * Support unwrapping the underlying causes from an error, including attached
66 stack trace contents if available.
67
68 Any reported error which implements the following interface:
69
70 ```go
71 type errorWithCause interface {
72 Unwrap() error
73 }
74 ```
75
76 will have the cause included as a previous error in the resulting event. The
77 cause information will be available on the Bugsnag dashboard and is available
78 for inspection in callbacks on the `errors.Error` object.
79
80 ```go
81 bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error {
82 if event.Error.Cause != nil {
83 fmt.Printf("This error was caused by %v", event.Error.Cause.Error())
84 }
85 return nil
86 })
87 ```
188
289 ## 1.7.0 (2020-11-18)
390
3535 You can download the code and its dependencies using
3636
3737 ```
38 go get -t github.com/bugsnag/bugsnag-go
38 go get -t github.com/bugsnag/bugsnag-go/v2
3939 ```
4040
4141 It will be put into "$GOPATH/src/github.com/bugsnag/bugsnag-go"
4949 You can run the tests with
5050
5151 ```shell
52 go test
52 go test ./...
5353 ```
5454
5555 Making PRs
7272 1. Decide on a version number and date for this release
7373 1. Add an entry (or update the `TBD` entry if it exists) for this release in `CHANGELOG.md` so that it includes the version number, release date and granular description of what changed
7474 1. Update the README if necessary
75 1. Update the version number in `bugsnag.go` and verify that tests pass.
75 1. Update the version number in `v2/bugsnag.go` and verify that tests pass.
7676 1. Commit these changes `git commit -am "Preparing release"`
7777 1. Create a PR from `next` -> `master` titled `Release vX.X.X`, adding a description to help the reviewer understand the scope of the release
7878 1. Await PR approval and CI pass
0 GIT
1 remote: https://github.com/bugsnag/maze-runner
2 revision: 7377529a77eb7585afc66cd2080fcdc4eea3306a
3 branch: v1
4 specs:
5 bugsnag-maze-runner (1.1.0)
6 cucumber (~> 3.1.0)
7 cucumber-expressions (= 5.0.15)
8 minitest (~> 5.0)
9 os (~> 1.0.0)
10 rack (~> 2.0.0)
11 rake (~> 12.3.3)
12 test-unit (~> 3.2.0)
13
14 GEM
15 remote: https://rubygems.org/
16 specs:
17 backports (3.21.0)
18 builder (3.2.4)
19 cucumber (3.1.0)
20 builder (>= 2.1.2)
21 cucumber-core (~> 3.1.0)
22 cucumber-expressions (~> 5.0.4)
23 cucumber-wire (~> 0.0.1)
24 diff-lcs (~> 1.3)
25 gherkin (~> 5.0)
26 multi_json (>= 1.7.5, < 2.0)
27 multi_test (>= 0.1.2)
28 cucumber-core (3.1.0)
29 backports (>= 3.8.0)
30 cucumber-tag_expressions (~> 1.1.0)
31 gherkin (>= 5.0.0)
32 cucumber-expressions (5.0.15)
33 cucumber-tag_expressions (1.1.1)
34 cucumber-wire (0.0.1)
35 diff-lcs (1.4.4)
36 gherkin (5.1.0)
37 minitest (5.14.4)
38 multi_json (1.15.0)
39 multi_test (0.1.2)
40 os (1.0.1)
41 power_assert (2.0.0)
42 rack (2.0.9)
43 rake (12.3.3)
44 test-unit (3.2.9)
45 power_assert
46
47 PLATFORMS
48 ruby
49
50 DEPENDENCIES
51 bugsnag-maze-runner!
52
53 BUNDLED WITH
54 2.1.4
00 TEST?=./...
1
2 export GO111MODULE=auto
13
24 default: alldeps test
35
0 # Upgrading guide
1
2 ## v1 to v2
3
4 The v2 release adds support for Go modules, removes web framework
5 integrations from the main repository, and supports library configuration
6 through environment variables. The following breaking changes occurred as a part
7 of this release:
8
9 ### Importing the package
10
11 ```diff+go
12 - import "github.com/bugsnag/bugsnag-go"
13 + import "github.com/bugsnag/bugsnag-go/v2"
14 ```
15
16 ### Removed `Configuration.Endpoint`
17
18 The `Endpoint` configuration option was deprecated as a part of the v1.4.0
19 release in November 2018. It was replaced with `Endpoints`, which includes
20 options for configuring both event and session delivery.
21
22 ```diff+go
23 - config.Endpoint = "https://notify.myserver.example.com"
24 + config.Endpoints = {
25 + Notify: "https://notify.myserver.example.com",
26 + Sessions: "https://sessions.myserver.example.com"
27 + }
28 ```
29
30 ### Moved web framework integrations into separate repositories
31
32 Integrations with Negroni, Revel, and Gin now live in separate repositories, to
33 prevent implicit dependencies on every framework and to improve the ease of
34 updating each integration independently.
35
36 ```diff+go
37 - import "github.com/bugsnag/bugsnag-go/negroni"
38 + import "github.com/bugsnag/bugsnag-go-negroni"
39 ```
40
41 ```diff+go
42 - import "github.com/bugsnag/bugsnag-go/revel"
43 + import "github.com/bugsnag/bugsnag-go-revel"
44 ```
45
46 ```diff+go
47 - import "github.com/bugsnag/bugsnag-go/gin"
48 + import "github.com/bugsnag/bugsnag-go-gin"
49 ```
50
51 ### Renamed constants for platform consistency
52
53 ```diff+go
54 - bugsnag.VERSION
55 + bugsnag.Version
56 ```
2020 )
2121
2222 // VERSION defines the version of this Bugsnag notifier
23 const VERSION = "1.7.0"
23 const VERSION = "1.9.0"
2424
2525 var panicHandlerOnce sync.Once
2626 var sessionTrackerOnce sync.Once
1515 // wherever the builtin error interface is expected.
1616 type Error struct {
1717 Err error
18 Cause *Error
1819 stack []uintptr
1920 frames []StackFrame
2021 }
3839 Error() string
3940 }
4041
42 type errorWithCause interface {
43 Unwrap() error
44 }
45
4146 // New makes an Error from the given value. If that value is already an
4247 // error then it will be used directly, if not, it will be passed to
4348 // fmt.Errorf("%v"). The skip parameter indicates how far up the stack
5257 return &Error{
5358 Err: e,
5459 stack: e.Callers(),
60 Cause: unwrapCause(e),
5561 }
5662 case errorWithStack:
5763 trace := e.StackTrace()
6167 }
6268 return &Error{
6369 Err: e,
70 Cause: unwrapCause(e),
6471 stack: stack,
6572 }
6673 case ErrorWithStackFrames:
7077 }
7178 return &Error{
7279 Err: e,
80 Cause: unwrapCause(e),
7381 stack: stack,
7482 frames: e.StackFrames(),
7583 }
8391 length := runtime.Callers(2+skip, stack[:])
8492 return &Error{
8593 Err: err,
94 Cause: unwrapCause(err),
8695 stack: stack[:length],
8796 }
8897 }
144153
145154 // TypeName returns the type this error. e.g. *errors.stringError.
146155 func (err *Error) TypeName() string {
147 if _, ok := err.Err.(uncaughtPanic); ok {
148 return "panic"
156 if p, ok := err.Err.(uncaughtPanic); ok {
157 return p.typeName
149158 }
150159 if name := reflect.TypeOf(err.Err).String(); len(name) > 0 {
151160 return name
152161 }
153162 return "error"
154163 }
164
165 func unwrapCause(err interface{}) *Error {
166 if causer, ok := err.(errorWithCause); ok {
167 cause := causer.Unwrap()
168 if cause == nil {
169 return nil
170 } else if hasStack(cause) { // avoid generating a (duplicate) stack from the current frame
171 return New(cause, 0)
172 } else {
173 return &Error{
174 Err: cause,
175 Cause: unwrapCause(cause),
176 stack: []uintptr{},
177 }
178 }
179 }
180 return nil
181 }
182
183 func hasStack(err error) bool {
184 if _, ok := err.(errorWithStack); ok {
185 return true
186 }
187 if _, ok := err.(ErrorWithStackFrames); ok {
188 return true
189 }
190 if _, ok := err.(ErrorWithCallers); ok {
191 return true
192 }
193 return false
194 }
204204 assertStacksMatch(t, expected, unwrapped.StackFrames())
205205 }
206206
207 type customErr struct {
208 msg string
209 cause error
210 callers []uintptr
211 }
212
213 func newCustomErr(msg string, cause error) error {
214 callers := make([]uintptr, 8)
215 runtime.Callers(2, callers)
216 return customErr{
217 msg: msg,
218 cause: cause,
219 callers: callers,
220 }
221 }
222
223 func (err customErr) Error() string {
224 return err.msg
225 }
226
227 func (err customErr) Unwrap() error {
228 return err.cause
229 }
230
231 func (err customErr) Callers() []uintptr {
232 return err.callers
233 }
234
235 func TestUnwrapCustomCause(t *testing.T) {
236 _, _, line, ok := runtime.Caller(0) // grab line immediately before error generators
237 err1 := fmt.Errorf("invalid token")
238 err2 := newCustomErr("login failed", err1)
239 err3 := newCustomErr("terminate process", err2)
240 unwrapped := New(err3, 0)
241 if !ok {
242 t.Fatalf("Something has gone wrong with loading the current stack")
243 }
244 if unwrapped.Error() != "terminate process" {
245 t.Errorf("Failed to unwrap error: %s", unwrapped.Error())
246 }
247 if unwrapped.Cause == nil {
248 t.Fatalf("Failed to capture cause error")
249 }
250 assertStacksMatch(t, []StackFrame{
251 StackFrame{Name: "TestUnwrapCustomCause", File: "errors/error_test.go", LineNumber: line + 3},
252 }, unwrapped.StackFrames())
253 if unwrapped.Cause.Error() != "login failed" {
254 t.Errorf("Failed to unwrap cause error: %s", unwrapped.Cause.Error())
255 }
256 if unwrapped.Cause.Cause == nil {
257 t.Fatalf("Failed to capture nested cause error")
258 }
259 assertStacksMatch(t, []StackFrame{
260 StackFrame{Name: "TestUnwrapCustomCause", File: "errors/error_test.go", LineNumber: line + 2},
261 }, unwrapped.Cause.StackFrames())
262 if unwrapped.Cause.Cause.Error() != "invalid token" {
263 t.Errorf("Failed to unwrap nested cause error: %s", unwrapped.Cause.Cause.Error())
264 }
265 if len(unwrapped.Cause.Cause.StackFrames()) > 0 {
266 t.Errorf("Did not expect cause to have a stack: %v", unwrapped.Cause.Cause.StackFrames())
267 }
268 if unwrapped.Cause.Cause.Cause != nil {
269 t.Fatalf("Extra cause detected: %v", unwrapped.Cause.Cause.Cause)
270 }
271 }
272
273 func TestUnwrapErrorsCause(t *testing.T) {
274 if !goVersionSupportsErrorWrapping() {
275 t.Skip("%w formatter is supported by go1.13+")
276 }
277 _, _, line, ok := runtime.Caller(0) // grab line immediately before error generators
278 err1 := fmt.Errorf("invalid token")
279 err2 := fmt.Errorf("login failed: %w", err1)
280 err3 := fmt.Errorf("terminate process: %w", err2)
281 unwrapped := New(err3, 0)
282 if !ok {
283 t.Fatalf("Something has gone wrong with loading the current stack")
284 }
285 if unwrapped.Error() != "terminate process: login failed: invalid token" {
286 t.Errorf("Failed to unwrap error: %s", unwrapped.Error())
287 }
288 assertStacksMatch(t, []StackFrame{
289 StackFrame{Name: "TestUnwrapErrorsCause", File: "errors/error_test.go", LineNumber: line + 4},
290 }, unwrapped.StackFrames())
291 if unwrapped.Cause == nil {
292 t.Fatalf("Failed to capture cause error")
293 }
294 if unwrapped.Cause.Error() != "login failed: invalid token" {
295 t.Errorf("Failed to unwrap cause error: %s", unwrapped.Cause.Error())
296 }
297 if len(unwrapped.Cause.StackFrames()) > 0 {
298 t.Errorf("Did not expect cause to have a stack: %v", unwrapped.Cause.StackFrames())
299 }
300 if unwrapped.Cause.Cause == nil {
301 t.Fatalf("Failed to capture nested cause error")
302 }
303 if len(unwrapped.Cause.Cause.StackFrames()) > 0 {
304 t.Errorf("Did not expect cause to have a stack: %v", unwrapped.Cause.Cause.StackFrames())
305 }
306 if unwrapped.Cause.Cause.Cause != nil {
307 t.Fatalf("Extra cause detected: %v", unwrapped.Cause.Cause.Cause)
308 }
309 }
310
311 func goVersionSupportsErrorWrapping() bool {
312 err1 := fmt.Errorf("inner error")
313 err2 := fmt.Errorf("outer error: %w", err1)
314 return err2.Error() == "outer error: inner error"
315 }
316
207317 func ExampleErrorf() {
208318 for i := 1; i <= 2; i++ {
209319 if i%2 == 1 {
44 "strings"
55 )
66
7 type uncaughtPanic struct{ message string }
7 type uncaughtPanic struct {
8 typeName string
9 message string
10 }
811
912 func (p uncaughtPanic) Error() string {
1013 return p.message
1417 // that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
1518 func ParsePanic(text string) (*Error, error) {
1619 lines := strings.Split(text, "\n")
20 prefixes := []string{"panic:", "fatal error:"}
1721
1822 state := "start"
1923
2024 var message string
25 var typeName string
2126 var stack []StackFrame
2227
2328 for i := 0; i < len(lines); i++ {
2429 line := lines[i]
2530
2631 if state == "start" {
27 if strings.HasPrefix(line, "panic: ") {
28 message = strings.TrimPrefix(line, "panic: ")
29 state = "seek"
30 } else {
32 for _, prefix := range prefixes {
33 if strings.HasPrefix(line, prefix) {
34 message = strings.TrimSpace(strings.TrimPrefix(line, prefix))
35 typeName = prefix[:len(prefix) - 1]
36 state = "seek"
37 break
38 }
39 }
40 if state == "start" {
3141 return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
3242 }
3343
3747 }
3848
3949 } else if state == "parsing" {
40 if line == "" {
50 if line == "" || strings.HasPrefix(line, "...") {
4151 state = "done"
4252 break
4353 }
6777 }
6878
6979 if state == "done" || state == "parsing" {
70 return &Error{Err: uncaughtPanic{message}, frames: stack}, nil
80 return &Error{Err: uncaughtPanic{typeName, message}, frames: stack}, nil
7181 }
7282 return nil, Errorf("could not parse panic: %v", text)
7383 }
9393 /0/c/go/src/pkg/net/http/server.go:1698 +0x91
9494 `
9595
96 var stackOverflow = `fatal error: stack overflow
97
98 runtime stack:
99 runtime.throw(0x10cd82b, 0xe)
100 /go/src/runtime/panic.go:1116 +0x72
101 runtime.newstack()
102 /go/src/runtime/stack.go:1060 +0x78d
103 runtime.morestack()
104 /go/src/runtime/asm_amd64.s:449 +0x8f
105
106 goroutine 1 [running]:
107 main.stackExhaustion.func1(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
108 /go/src/app/cases.go:42 +0x74 fp=0xc020161be0 sp=0xc020161bd8 pc=0x10a7774
109 main.stackExhaustion.func1(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
110 /go/src/app/cases.go:43 +0x5f fp=0xc020163b30 sp=0xc020161be0 pc=0x10a775f
111 main.stackExhaustion.func1(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
112 /go/src/app/cases.go:43 +0x5f fp=0xc020165a80 sp=0xc020163b30 pc=0x10a775f
113 main.stackExhaustion.func1(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
114 /go/src/app/cases.go:43 +0x5f fp=0xc0201679d0 sp=0xc020165a80 pc=0x10a775f
115 main.stackExhaustion.func1(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
116 /go/src/app/cases.go:43 +0x5f fp=0xc0201679d0 sp=0xc020165a80 pc=0x10a775f
117 ...additional frames elided...
118 `
119
96120 var result = []StackFrame{
97 StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"},
98 StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"},
99 StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"},
121 {File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"},
122 {File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"},
123 {File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"},
100124 }
101125
102126 var resultCreatedBy = append(result,
139163 }
140164 }
141165 }
166
167 var concurrentMapReadWrite = `fatal error: concurrent map read and map write
168
169 goroutine 1 [running]:
170 runtime.throw(0x10766f5, 0x21)
171 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/panic.go:1116 +0x72 fp=0xc00003a6c8 sp=0xc00003a698 pc=0x102d592
172 runtime.mapaccess1_faststr(0x1066fc0, 0xc000060000, 0x10732e0, 0x1, 0xc000100088)
173 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/map_faststr.go:21 +0x465 fp=0xc00003a738 sp=0xc00003a6c8 pc=0x100e9c5
174 main.concurrentWrite()
175 /myapps/go/fatalerror/main.go:14 +0x7a fp=0xc00003a778 sp=0xc00003a738 pc=0x105d83a
176 main.main()
177 /myapps/go/fatalerror/main.go:41 +0x25 fp=0xc00003a788 sp=0xc00003a778 pc=0x105d885
178 runtime.main()
179 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/proc.go:204 +0x209 fp=0xc00003a7e0 sp=0xc00003a788 pc=0x102fd49
180 runtime.goexit()
181 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/asm_amd64.s:1374 +0x1 fp=0xc00003a7e8 sp=0xc00003a7e0 pc=0x105a4a1
182
183 goroutine 5 [runnable]:
184 main.concurrentWrite.func1(0xc000060000)
185 /myapps/go/fatalerror/main.go:10 +0x4c
186 created by main.concurrentWrite
187 /myapps/go/fatalerror/main.go:8 +0x4b
188 `
189
190 func TestParseFatalError(t *testing.T) {
191
192 Err, err := ParsePanic(concurrentMapReadWrite)
193
194 if err != nil {
195 t.Fatal(err)
196 }
197
198 if Err.TypeName() != "fatal error" {
199 t.Errorf("Wrong type: %s", Err.TypeName())
200 }
201
202 if Err.Error() != "concurrent map read and map write" {
203 t.Errorf("Wrong message: '%s'", Err.Error())
204 }
205
206 if Err.StackFrames()[0].Func() != nil {
207 t.Errorf("Somehow managed to find a func...")
208 }
209
210 var result = []StackFrame{
211 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/panic.go", LineNumber: 1116, Name: "throw", Package: "runtime"},
212 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/map_faststr.go", LineNumber: 21, Name: "mapaccess1_faststr", Package: "runtime"},
213 StackFrame{File: "/myapps/go/fatalerror/main.go", LineNumber: 14, Name: "concurrentWrite", Package: "main"},
214 StackFrame{File: "/myapps/go/fatalerror/main.go", LineNumber: 41, Name: "main", Package: "main"},
215 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/proc.go", LineNumber: 204, Name: "main", Package: "runtime"},
216 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/asm_amd64.s", LineNumber: 1374, Name: "goexit", Package: "runtime"},
217 }
218
219 if !reflect.DeepEqual(Err.StackFrames(), result) {
220 t.Errorf("Wrong stack for concurrent write fatal error:")
221 for i, frame := range result {
222 t.Logf("[%d] %#v", i, frame)
223 if len(Err.StackFrames()) > i {
224 t.Logf(" %#v", Err.StackFrames()[i])
225 }
226 }
227 }
228 }
229
230 func TestParseStackOverflow(t *testing.T) {
231 Err, err := ParsePanic(stackOverflow)
232
233 if err != nil {
234 t.Fatal(err)
235 }
236
237 if Err.TypeName() != "fatal error" {
238 t.Errorf("Wrong type: %s", Err.TypeName())
239 }
240
241 if Err.Error() != "stack overflow" {
242 t.Errorf("Wrong message: '%s'", Err.Error())
243 }
244
245 if Err.StackFrames()[0].Func() != nil {
246 t.Errorf("Somehow managed to find a func...")
247 }
248
249 var result = []StackFrame{
250 {File: "/go/src/app/cases.go", LineNumber: 42, Name: "stackExhaustion.func1", Package: "main"},
251 {File: "/go/src/app/cases.go", LineNumber: 43, Name: "stackExhaustion.func1", Package: "main"},
252 {File: "/go/src/app/cases.go", LineNumber: 43, Name: "stackExhaustion.func1", Package: "main"},
253 {File: "/go/src/app/cases.go", LineNumber: 43, Name: "stackExhaustion.func1", Package: "main"},
254 {File: "/go/src/app/cases.go", LineNumber: 43, Name: "stackExhaustion.func1", Package: "main"},
255 }
256
257 if !reflect.DeepEqual(Err.StackFrames(), result) {
258 t.Errorf("Wrong stack:")
259 for i, frame := range result {
260 t.Logf("[%d] %#v", i, frame)
261 if len(Err.StackFrames()) > i {
262 t.Logf(" %#v", Err.StackFrames()[i])
263 }
264 }
265 }
266 }
178178 }
179179 }
180180
181 event.Stacktrace = generateStacktrace(err, config)
182
183 for _, callback := range callbacks {
184 callback(event)
185 if event.Severity != event.handledState.OriginalSeverity {
186 event.handledState.SeverityReason = SeverityReasonCallbackSpecified
187 }
188 }
189
190 return event, config
191 }
192
193 func generateStacktrace(err *errors.Error, config *Configuration) []StackFrame {
194 stack := make([]StackFrame, len(err.StackFrames()))
181195 for i, frame := range err.StackFrames() {
182196 file := frame.File
183197 inProject := config.isProjectPackage(frame.Package)
190204 file = config.stripProjectPackages(file)
191205 }
192206
193 event.Stacktrace[i] = StackFrame{
207 stack[i] = StackFrame{
194208 Method: frame.Name,
195209 File: file,
196210 LineNumber: frame.LineNumber,
198212 }
199213 }
200214
201 for _, callback := range callbacks {
202 callback(event)
203 if event.Severity != event.handledState.OriginalSeverity {
204 event.handledState.SeverityReason = SeverityReasonCallbackSpecified
205 }
206 }
207
208 return event, config
215 return stack
209216 }
210217
211218 func populateEventWithContext(ctx context.Context, event *Event) {
+0
-18
examples/gin/README.md less more
0 # Example Gin application
1
2 This package contains an example Gin application, with Bugsnag configured.
3
4 ## Run the example
5
6 1. Change the API key in `main.go` to a project you've created in [Bugsnag](https://app.bugsnag.com).
7 1. Inside `bugsnag-go/examples/gin` do:
8 ```bash
9 go get
10 go run main.go
11 ```
12 1. The application is now running. You can now visit
13 ```
14 http://localhost:9001/unhandled - to trigger an unhandled panic
15 http://localhost:9001/handled - to trigger a handled error
16 ```
17 1. You should now see events for these exceptions in your [Bugsnag dashboard](https://app.bugsnag.com).
+0
-49
examples/gin/main.go less more
0 package main
1
2 import (
3 "fmt"
4 "net/http"
5 "os"
6
7 "github.com/bugsnag/bugsnag-go"
8 "github.com/bugsnag/bugsnag-go/gin"
9 "github.com/gin-gonic/gin"
10 )
11
12 // Insert your API key
13 const apiKey = "YOUR-API-KEY-HERE"
14
15 func main() {
16 if len(apiKey) != 32 {
17 fmt.Println("Please set your API key in main.go before running example.")
18 return
19 }
20
21 g := gin.Default()
22
23 g.Use(bugsnaggin.AutoNotify(bugsnag.Configuration{APIKey: apiKey}))
24
25 g.GET("/unhandled", performUnhandledCrash)
26 g.GET("/handled", performHandledError)
27
28 fmt.Println("=============================================================================")
29 fmt.Println("Visit http://localhost:9001/unhandled - To perform an unhandled crash")
30 fmt.Println("Visit http://localhost:9001/handled - To create a manual error notification")
31 fmt.Println("=============================================================================")
32 fmt.Println("")
33
34 g.Run(":9001") // listen and serve on 0.0.0.0:9001
35 }
36
37 func performUnhandledCrash(c *gin.Context) {
38 c.String(http.StatusOK, "OK")
39 // Invalid type assertion, will panic
40 func(a interface{}) string { return a.(string) }(struct{}{})
41 }
42
43 func performHandledError(c *gin.Context) {
44 c.String(http.StatusOK, "OK")
45 if _, err := os.Open("nonexistent_file.txt"); err != nil {
46 bugsnag.Notify(err, c.Request.Context())
47 }
48 }
44 "net/http"
55 "os"
66
7 "github.com/bugsnag/bugsnag-go"
7 "github.com/bugsnag/bugsnag-go/v2"
88 )
99
1010 // Insert your API key
+0
-18
examples/martini/README.md less more
0 # Example Martini application
1
2 This package contains an example Martini application, with Bugsnag configured.
3
4 ## Run the example
5
6 1. Change the API key in `main.go` to a project you've created in [Bugsnag](https://app.bugsnag.com).
7 1. Inside `bugsnag-go/examples/martini` do:
8 ```bash
9 go get
10 go run main.go
11 ```
12 1. The application is now running. You can now visit
13 ```
14 http://localhost:9001/unhandled - to trigger an unhandled panic
15 http://localhost:9001/handled - to trigger a handled error
16 ```
17 1. You should now see events for these exceptions in your [Bugsnag dashboard](https://app.bugsnag.com).
+0
-50
examples/martini/main.go less more
0 package main
1
2 import (
3 "fmt"
4 "net/http"
5 "os"
6
7 "github.com/bugsnag/bugsnag-go"
8 "github.com/bugsnag/bugsnag-go/martini"
9 "github.com/go-martini/martini"
10 )
11
12 // Insert your API key
13 const apiKey = "YOUR-API-KEY-HERE"
14
15 func main() {
16 if len(apiKey) != 32 {
17 fmt.Println("Please set the API key in main.go before running the example")
18 return
19 }
20
21 bugsnag.Configure(bugsnag.Configuration{APIKey: apiKey})
22 m := martini.Classic()
23
24 m.Use(martini.Recovery())
25 // Add bugsnag handler after martini.Recovery() to ensure panics get picked up
26 m.Use(bugsnagmartini.AutoNotify())
27
28 m.Get("/unhandled", performUnhandledCrash)
29 m.Get("/handled", performHandledError)
30
31 fmt.Println("=============================================================================")
32 fmt.Println("Visit http://localhost:9001/unhandled - To perform an unhandled crash")
33 fmt.Println("Visit http://localhost:9001/handled - To create a manual error notification")
34 fmt.Println("=============================================================================")
35 fmt.Println("")
36
37 m.RunOnAddr(":9001")
38 }
39
40 func performUnhandledCrash() {
41 // Invalid type assertion, will panic
42 func(a interface{}) string { return a.(string) }(struct{}{})
43 }
44
45 func performHandledError(r *http.Request) {
46 if _, err := os.Open("nonexistent_file.txt"); err != nil {
47 bugsnag.Notify(err, r.Context())
48 }
49 }
+0
-18
examples/negroni/README.md less more
0 # Example Negroni application
1
2 This package contains an example Negroni application, with Bugsnag configured.
3
4 ## Run the example
5
6 1. Change the API key in `main.go` to a project you've created in [Bugsnag](https://app.bugsnag.com).
7 1. Inside `bugsnag-go/examples/negroni` do:
8 ```bash
9 go get
10 go run main.go
11 ```
12 1. The application is now running. You can now visit
13 ```
14 http://localhost:9001/unhandled - to trigger an unhandled panic
15 http://localhost:9001/handled - to trigger a handled error
16 ```
17 1. You should now see events for these exceptions in your [Bugsnag dashboard](https://app.bugsnag.com).
+0
-58
examples/negroni/main.go less more
0 package main
1
2 import (
3 "fmt"
4 "net/http"
5 "os"
6
7 "github.com/bugsnag/bugsnag-go"
8 "github.com/bugsnag/bugsnag-go/negroni"
9 "github.com/urfave/negroni"
10 )
11
12 // Insert your API key
13 const apiKey = "YOUR API KEY"
14
15 func main() {
16 if len(apiKey) != 32 {
17 fmt.Println("Please set your API key in main.go before running example.")
18 return
19 }
20
21 bugsnag.Configure(bugsnag.Configuration{APIKey: apiKey})
22
23 mux := http.NewServeMux()
24 mux.HandleFunc("/unhandled", unhandledCrash)
25 mux.HandleFunc("/handled", handledError)
26
27 n := negroni.New()
28 n.Use(negroni.NewRecovery())
29 // Add bugsnag handler after negroni.NewRecovery() to ensure panics get picked up
30 n.Use(bugsnagnegroni.AutoNotify())
31 n.UseHandler(mux)
32
33 fmt.Println("=============================================================================")
34 fmt.Println("Visit http://localhost:9001/unhandled - To perform an unhandled crash")
35 fmt.Println("Visit http://localhost:9001/handled - To create a manual error notification")
36 fmt.Println("=============================================================================")
37 fmt.Println("")
38
39 http.ListenAndServe(":9001", n)
40 }
41
42 func unhandledCrash(w http.ResponseWriter, r *http.Request) {
43 w.WriteHeader(200)
44 w.Write([]byte("OK\n"))
45
46 // Invalid type assertion, will panic
47 func(a interface{}) string { return a.(string) }(struct{}{})
48 }
49
50 func handledError(w http.ResponseWriter, r *http.Request) {
51 w.WriteHeader(200)
52 w.Write([]byte("OK\n"))
53 _, err := os.Open("nonexistent_file.txt")
54 if err != nil {
55 bugsnag.Notify(err, r.Context())
56 }
57 }
+0
-3
examples/revelapp/.gitignore less more
0 test-results/
1 tmp/
2 routes/
+0
-23
examples/revelapp/README.md less more
0 # Example Revel application
1
2 This package contains an example Revel application, with Bugsnag configured.
3
4 The key files for integrating Bugsnag are:
5
6 1. `app/init.go` - Sets up the Bugsnag filter.
7 1. `app/controllers/app.go` - notifies about an handled error and an unhandled panic
8 1. `conf/app.conf` - configures Bugsnag, in particular the API key
9
10 ## Run the example
11
12 1. Change the API key in `app.conf` to a project you've created in [Bugsnag](https://app.bugsnag.com).
13 1. Inside `bugsnag-go/examples/revelapp` do:
14 ```bash
15 revel run
16 ```
17 1. The application is now running. You can now visit
18 ```
19 http://localhost:9001/unhandled - to trigger an unhandled panic
20 http://localhost:9001/handled - to trigger a handled error
21 ```
22 1. You should now see events for these exceptions in your [Bugsnag dashboard](https://app.bugsnag.com).
+0
-30
examples/revelapp/app/controllers/app.go less more
0 package controllers
1
2 import (
3 "fmt"
4
5 bugsnag "github.com/bugsnag/bugsnag-go"
6 "github.com/revel/revel"
7 )
8
9 type App struct {
10 *revel.Controller
11 }
12
13 func (c App) Index() revel.Result {
14 return c.Render()
15 }
16
17 func (c App) Handled() revel.Result {
18 bugsnag.Notify(fmt.Errorf("oopsie"), c.Args["context"])
19 return c.Render()
20 }
21
22 func (c App) Unhandled() revel.Result {
23 crash(struct{}{})
24 return c.Render()
25 }
26
27 func crash(a interface{}) string {
28 return a.(string)
29 }
+0
-45
examples/revelapp/app/init.go less more
0 package app
1
2 import (
3 "github.com/bugsnag/bugsnag-go/revel"
4 "github.com/revel/revel"
5 )
6
7 var (
8 // AppVersion revel app version (ldflags)
9 AppVersion string
10
11 // BuildTime revel app build-time (ldflags)
12 BuildTime string
13 )
14
15 func init() {
16 // Filters is the default set of global filters.
17 revel.Filters = []revel.Filter{
18 revel.PanicFilter, // Recover from panics and display an error page instead.
19 bugsnagrevel.Filter, // Send panics to Bugsnag
20 revel.RouterFilter, // Use the routing table to select the right Action
21 revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
22 revel.ParamsFilter, // Parse parameters into Controller.Params.
23 revel.SessionFilter, // Restore and write the session cookie.
24 revel.FlashFilter, // Restore and write the flash cookie.
25 revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
26 revel.I18nFilter, // Resolve the requested language
27 HeaderFilter, // Add some security based headers
28 revel.InterceptorFilter, // Run interceptors around the action.
29 revel.CompressFilter, // Compress the result.
30 revel.ActionInvoker, // Invoke the action.
31 }
32 }
33
34 // HeaderFilter adds common security headers
35 // There is a full implementation of a CSRF filter in
36 // https://github.com/revel/modules/tree/master/csrf
37 var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
38 c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
39 c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
40 c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
41 c.Response.Out.Header().Add("Referrer-Policy", "strict-origin-when-cross-origin")
42
43 fc[0](c, fc[1:]) // Execute the next filter stage.
44 }
+0
-21
examples/revelapp/app/views/App/Index.html less more
0 {{set . "title" "Home"}}
1 {{template "header.html" .}}
2
3 <header class="jumbotron" style="background-color:#A9F16C">
4 <div class="container">
5 <div class="row">
6 <h1>It works!</h1>
7 <p></p>
8 </div>
9 </div>
10 </header>
11
12 <div class="container">
13 <div class="row">
14 <div class="span6">
15 {{template "flash.html" .}}
16 </div>
17 </div>
18 </div>
19
20 {{template "footer.html" .}}
+0
-64
examples/revelapp/app/views/debug.html less more
0 <style type="text/css">
1 #sidebar {
2 position: absolute;
3 right: 0px;
4 top:69px;
5 max-width: 75%;
6 z-index: 1000;
7 background-color: #fee;
8 border: thin solid grey;
9 padding: 10px;
10 }
11 #toggleSidebar {
12 position: absolute;
13 right: 0px;
14 top: 50px;
15 background-color: #fee;
16 }
17
18 </style>
19 <div id="sidebar" style="display:none;">
20 <h4>Available pipelines</h4>
21 <dl>
22 {{ range $index, $value := .}}
23 <dt>{{$index}}</dt>
24 <dd>{{$value}}</dd>
25 {{end}}
26 </dl>
27 <h4>Flash</h4>
28 <dl>
29 {{ range $index, $value := .flash}}
30 <dt>{{$index}}</dt>
31 <dd>{{$value}}</dd>
32 {{end}}
33 </dl>
34
35 <h4>Errors</h4>
36 <dl>
37 {{ range $index, $value := .errors}}
38 <dt>{{$index}}</dt>
39 <dd>{{$value}}</dd>
40 {{end}}
41 </dl>
42 </div>
43 <a id="toggleSidebar" href="#" class="toggles"><i class="glyphicon glyphicon-chevron-left"></i></a>
44
45 <script>
46 $sidebar = 0;
47 $('#toggleSidebar').click(function() {
48 if ($sidebar === 1) {
49 $('#sidebar').hide();
50 $('#toggleSidebar i').addClass('glyphicon-chevron-left');
51 $('#toggleSidebar i').removeClass('glyphicon-chevron-right');
52 $sidebar = 0;
53 }
54 else {
55 $('#sidebar').show();
56 $('#toggleSidebar i').addClass('glyphicon-chevron-right');
57 $('#toggleSidebar i').removeClass('glyphicon-chevron-left');
58 $sidebar = 1;
59 }
60
61 return false;
62 });
63 </script>
+0
-20
examples/revelapp/app/views/errors/404.html less more
0 <!DOCTYPE html>
1 <html lang="en">
2 <head>
3 <title>Not found</title>
4 </head>
5 <body>
6 {{if eq .RunMode "dev"}}
7 {{template "errors/404-dev.html" .}}
8 {{else}}
9 {{with .Error}}
10 <h1>
11 {{.Title}}
12 </h1>
13 <p>
14 {{.Description}}
15 </p>
16 {{end}}
17 {{end}}
18 </body>
19 </html>
+0
-16
examples/revelapp/app/views/errors/500.html less more
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Application error</title>
4 </head>
5 <body>
6 {{if eq .RunMode "dev"}}
7 {{template "errors/500-dev.html" .}}
8 {{else}}
9 <h1>Oops, an error occurred</h1>
10 <p>
11 This exception has been logged.
12 </p>
13 {{end}}
14 </body>
15 </html>
+0
-18
examples/revelapp/app/views/flash.html less more
0 {{if .flash.success}}
1 <div class="alert alert-success">
2 {{.flash.success}}
3 </div>
4 {{end}}
5
6 {{if or .errors .flash.error}}
7 <div class="alert alert-danger">
8 {{if .flash.error}}
9 {{.flash.error}}
10 {{end}}
11 <ul style="margin-top:10px;">
12 {{range .errors}}
13 <li>{{.}}</li>
14 {{end}}
15 </ul>
16 </div>
17 {{end}}
+0
-5
examples/revelapp/app/views/footer.html less more
0 {{if eq .RunMode "dev"}}
1 {{template "debug.html" .}}
2 {{end}}
3 </body>
4 </html>
+0
-19
examples/revelapp/app/views/header.html less more
0 <!DOCTYPE html>
1
2 <html>
3 <head>
4 <title>{{.title}}</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <link rel="stylesheet" type="text/css" href="/public/css/bootstrap-3.3.6.min.css">
8 <link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
9 <script src="/public/js/jquery-2.2.4.min.js"></script>
10 <script src="/public/js/bootstrap-3.3.6.min.js"></script>
11 {{range .moreStyles}}
12 <link rel="stylesheet" type="text/css" href="/public/{{.}}">
13 {{end}}
14 {{range .moreScripts}}
15 <script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
16 {{end}}
17 </head>
18 <body>
+0
-211
examples/revelapp/conf/app.conf less more
0 ################################################################################
1 # Revel configuration file
2 # More info at http://revel.github.io/manual/appconf.html
3 ################################################################################
4
5 # Sets `revel.AppName` for use in-app.
6 # Example:
7 # `if revel.AppName {...}`
8 app.name = revelapp
9
10 # Bugsnag config
11 bugsnag.apikey=YOUR-API-KEY-HERE
12
13 # A secret string which is passed to cryptographically sign the cookie to prevent
14 # (and detect) user modification.
15 # Keep this string secret or users will be able to inject arbitrary cookie values
16 # into your application
17 app.secret = ziL732qLyDQgjwQUktLFswLC8HhhwiJlhGsZbDLC85Fc8RgB4H1TVEhmk3JRybz0
18
19 # Revel running behind proxy like nginx, haproxy, etc.
20 app.behind.proxy = false
21
22
23 # The IP address on which to listen.
24 http.addr =
25
26 # The port on which to listen.
27 http.port = 9001
28
29 # Whether to use SSL or not.
30 http.ssl = false
31
32 # Path to an X509 certificate file, if using SSL.
33 #http.sslcert =
34
35 # Path to an X509 certificate key, if using SSL.
36 #http.sslkey =
37
38
39 # Timeout specifies a time limit for request (in seconds) made by a single client.
40 # A Timeout of zero means no timeout.
41 http.timeout.read = 90
42 http.timeout.write = 60
43
44
45 # For any cookies set by Revel (Session,Flash,Error) these properties will set
46 # the fields of:
47 # http://golang.org/pkg/net/http/#Cookie
48 #
49 # Each cookie set by Revel is prefixed with this string.
50 cookie.prefix = REVEL
51
52 # A secure cookie has the secure attribute enabled and is only used via HTTPS,
53 # ensuring that the cookie is always encrypted when transmitting from client to
54 # server. This makes the cookie less likely to be exposed to cookie theft via
55 # eavesdropping.
56 #
57 # Defaults to false. If 'http.ssl' is enabled, this will be defaulted to true.
58 # This should only be true when Revel is handling SSL connections. If you are
59 # using a proxy in front of revel (Nginx, Apache, etc), then this should be left
60 # as false.
61 # cookie.secure = false
62
63 # Limit cookie access to a given domain.
64 #cookie.domain =
65
66 # Define when your session cookie expires.
67 # Values:
68 # "720h"
69 # A time duration (http://golang.org/pkg/time/#ParseDuration) after which
70 # the cookie expires and the session is invalid.
71 # "session"
72 # Sets a session cookie which invalidates the session when the user close
73 # the browser.
74 session.expires = 720h
75
76
77 # The date format used by Revel. Possible formats defined by the Go `time`
78 # package (http://golang.org/pkg/time/#Parse)
79 format.date = 2006-01-02
80 format.datetime = 2006-01-02 15:04
81
82
83 # Determines whether the template rendering should use chunked encoding.
84 # Chunked encoding can decrease the time to first byte on the client side by
85 # sending data before the entire template has been fully rendered.
86 results.chunked = false
87
88
89 # The default language of this application.
90 i18n.default_language = en
91
92 # The default format when message is missing.
93 # The original message shows in %s
94 #i18n.unknown_format = "??? %s ???"
95
96
97 # Module to serve static content such as CSS, JavaScript and Media files
98 # Allows Routes like this:
99 # `Static.ServeModule("modulename","public")`
100 module.static = github.com/revel/modules/static
101
102
103
104 ################################################################################
105
106 # Section: dev
107 # This section is evaluated when running Revel in dev mode. Like so:
108 # `revel run path/to/myapp`
109 [dev]
110
111 # This sets `revel.DevMode` for use in-app.
112 # Example:
113 # `if revel.DevMode {...}`
114 # or in your templates with
115 # `<no value>`
116 # Values:
117 # "true"
118 # Sets `DevMode` to `true`.
119 # "false"
120 # Sets `DevMode` to `false`.
121 mode.dev = true
122
123
124 # Pretty print JSON/XML when calling RenderJSON/RenderXML
125 # Values:
126 # "true"
127 # Enables pretty printing.
128 # "false"
129 # Disables pretty printing.
130 results.pretty = true
131
132
133 # Watch your applicaton files for changes and automatically rebuild
134 # Values:
135 # "true"
136 # Enables auto rebuilding.
137 # "false"
138 # Disables auto rebuilding.
139 watch = true
140
141
142 # Define when to rebuild new changes.
143 # Values:
144 # "normal"
145 # Rebuild when a new request is received and changes have been detected.
146 # "eager"
147 # Rebuild as soon as changes are detected.
148 watch.mode = normal
149
150 # Watch the entire `$GOPATH` for changes.
151 # Values:
152 # "true"
153 # Includes `$GOPATH` in watch path.
154 # "false"
155 # Excludes `$GOPATH` from watch path. Default value.
156 #watch.gopath = true
157
158
159 # Module to run code tests in the browser
160 # See:
161 # http://revel.github.io/manual/testing.html
162 module.testrunner = github.com/revel/modules/testrunner
163
164
165 # Where to log the various Revel logs
166 # Values:
167 # "off"
168 # Disable log output.
169 # "stdout"
170 # Log to OS's standard output.
171 # "stderr"
172 # Log to Os's standard error output. Default value.
173 # "relative/path/to/log"
174 # Log to file.
175 log.all.filter.module.app = stdout # Log all loggers for the application to the stdout
176 log.error.nfilter.module.app = stderr # Everything else that logs an error to stderr
177 log.crit.output = stderr # Everything that logs something as critical goes to this
178
179 # Revel request access log
180 # Access log line format:
181 # INFO 21:53:55 static server-engine.go:169: Request Stats ip=127.0.0.1 path=/public/vendors/datatables.net-buttons/js/buttons.html5.min.js method=GET start=2017/08/31 21:53:55 status=200 duration_seconds=0.0002583 section=requestlog
182 log.request.output = stdout
183
184
185
186 ################################################################################
187 # Section: prod
188 # This section is evaluated when running Revel in production mode. Like so:
189 # `revel run path/to/myapp prod`
190 # See:
191 # [dev] section for documentation of the various settings
192 [prod]
193
194 mode.dev = false
195
196 results.pretty = false
197
198 watch = false
199
200 module.testrunner =
201
202 log.warn.output = log/%(app.name)-warn.json # Log all warn messages to file
203 log.error.output = log/%(app.name)-error.json # Log all errors to file
204 log.crit.output = log/%(app.name)-critical.json # Log all critical to file
205
206 # Revel request access log (json format)
207 # Example:
208 # log.request.output = %(app.name)s-request.json
209 log.request.output = log/%(app.name)s-requests.json
210
+0
-29
examples/revelapp/conf/routes less more
0 # Routes Config
1 #
2 # This file defines all application routes (Higher priority routes first)
3 #
4
5 module:testrunner
6 # module:jobs
7
8
9 GET / App.Index
10
11 # Ignore favicon requests
12 GET /favicon.ico 404
13
14 # Map static resources from the /app/public folder to the /public path
15 GET /public/*filepath Static.Serve("public")
16
17 GET /handled App.Handled
18 GET /unhandled App.Unhandled
19
20 # Catch all, this will route any request into the controller path
21 #
22 # **** WARNING ****
23 # Enabling this exposes any controller and function to the web.
24 # ** This is a serious security issue if used online **
25 #
26 # For rapid development uncomment the following to add new controller.action endpoints
27 # without having to add them to the routes table.
28 # * /:controller/:action :controller.:action
+0
-7
examples/revelapp/messages/sample.en less more
0 # Sample messages file for the English language (en)
1 # Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
2 # Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
3 # See also:
4 # - http://www.rfc-editor.org/rfc/bcp/bcp47.txt
5 # - http://www.w3.org/International/questions/qa-accept-lang-locales
6
+0
-5
examples/revelapp/public/css/bootstrap-3.3.6.min.css less more
0 /*!
1 * Bootstrap v3.3.6 (http://getbootstrap.com)
2 * Copyright 2011-2015 Twitter, Inc.
3 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
4 *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */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,menu,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-color:transparent}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}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!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:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{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.woff2) format('woff2'),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:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-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}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;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}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;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}[role=button]{cursor:pointer}.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,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,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,.h5,.h6,h4,h5,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%}.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:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{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}dd,dt{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[data-original-title],abbr[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 ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer: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 .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}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%;font-weight:700;-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-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.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-md-1,.col-md-10,.col-md-11,.col-md-12,.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-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.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-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{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-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{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-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{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-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{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}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{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>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{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>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{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=checkbox],input[type=radio]{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=checkbox]:focus,input[type=radio]: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:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],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-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],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}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;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{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{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 .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{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 .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{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~.form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{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{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.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;-ms-touch-action:manipulation;touch-action:manipulation;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.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{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{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{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.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{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.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{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.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{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=button].btn-block,input[type=reset].btn-block,input[type=submit].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-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{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:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{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 dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@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-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.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,.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:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-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-left-radius:4px;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-right-radius:4px;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=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.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 .form-control:focus{z-index:3}.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.3333333;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}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.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}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn: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=checkbox],.input-group-addon input[type=radio]{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-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){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:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{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:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{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{z-index:2;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:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.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:focus,.nav-tabs>li.active>a:hover{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:focus,.nav-tabs.nav-justified>.active>a:hover{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:focus,.nav-tabs.nav-justified>.active>a:hover{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:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.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:focus,.nav-tabs-justified>.active>a:hover{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:focus,.nav-tabs-justified>.active>a:hover{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-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{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-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{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:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@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 .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{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-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 .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{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}.navbar-form .form-group:last-child{margin-bottom:0}}@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-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{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;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}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{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:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{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:focus,.navbar-default .navbar-nav>.open>a:hover{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:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{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:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{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:focus,.navbar-inverse .navbar-nav>.open>a:hover{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:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{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:#337ab7;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:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{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;line-height:1.3333333}.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;line-height:1.5}.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:focus,.pager li>a:hover{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:focus,.pager .disabled>a:hover,.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:focus,a.label:hover{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]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{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:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom: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,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .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:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.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:#337ab7;-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-bar-striped,.progress-striped .progress-bar{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-bar.active,.progress.active .progress-bar{-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-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{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.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}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{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>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>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,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.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,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.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,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.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>.list-group,.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:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.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 embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.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:focus,.close:hover{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:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(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{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-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.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;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{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;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;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-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;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;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);line-break:auto}.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;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>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.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);background-color:rgba(0,0,0,0);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:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.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-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row: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}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.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!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.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!important}tr.visible-md{display:table-row!important}td.visible-md,th.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!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.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!important}tr.visible-print{display:table-row!important}td.visible-print,th.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}}
examples/revelapp/public/fonts/glyphicons-halflings-regular.ttf less more
Binary diff not shown
examples/revelapp/public/fonts/glyphicons-halflings-regular.woff less more
Binary diff not shown
examples/revelapp/public/fonts/glyphicons-halflings-regular.woff2 less more
Binary diff not shown
examples/revelapp/public/img/favicon.png less more
Binary diff not shown
+0
-7
examples/revelapp/public/js/bootstrap-3.3.6.min.js less more
0 /*!
1 * Bootstrap v3.3.6 (http://getbootstrap.com)
2 * Copyright 2011-2015 Twitter, Inc.
3 * Licensed under the MIT license
4 */
5 if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");
6 d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
+0
-4
examples/revelapp/public/js/jquery-2.2.4.min.js less more
0 /*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */
1 !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=la(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=ma(b);function pa(){}pa.prototype=d.filters=d.pseudos,d.setFilters=new pa,g=fa.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=R.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=S.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(Q," ")}),h=h.slice(c.length));for(g in d.filter)!(e=W[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fa.error(a):z(a,i).slice(0)};function qa(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){n.each(b,function(b,c){n.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==n.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return n.each(arguments,function(a,b){var c;while((c=n.inArray(b,f,c))>-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c;
2 }catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=N.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function W(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&T.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var X=/^(?:checkbox|radio)$/i,Y=/<([\w:-]+)/,Z=/^$|\/(?:java|ecma)script/i,$={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,e,f,g=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||d,e=c.documentElement,f=c.body,a.pageX=b.clientX+(e&&e.scrollLeft||f&&f.scrollLeft||0)-(e&&e.clientLeft||f&&f.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||f&&f.scrollTop||0)-(e&&e.clientTop||f&&f.clientTop||0)),a.which||void 0===g||(a.which=1&g?1:2&g?3:4&g?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,e,f=a.type,g=a,h=this.fixHooks[f];h||(this.fixHooks[f]=h=ea.test(f)?this.mouseHooks:da.test(f)?this.keyHooks:{}),e=h.props?this.props.concat(h.props):this.props,a=new n.Event(g),b=e.length;while(b--)c=e[b],a[c]=g[c];return a.target||(a.target=d),3===a.target.nodeType&&(a.target=a.target.parentNode),h.filter?h.filter(a,g):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==ia()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===ia()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ga:ha):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={constructor:n.Event,isDefaultPrevented:ha,isPropagationStopped:ha,isImmediatePropagationStopped:ha,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ga,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ga,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ga,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||n.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),n.fn.extend({on:function(a,b,c,d){return ja(this,a,b,c,d)},one:function(a,b,c,d){return ja(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=ha),this.each(function(){n.event.remove(this,a,c,b)})}});var ka=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,la=/<script|<style|<link/i,ma=/checked\s*(?:[^=]|=\s*.checked.)/i,na=/^true\/(.*)/,oa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=wa[0].contentDocument,b.write(),b.close(),c=ya(a,b),wa.detach()),xa[a]=c),c}var Aa=/^margin/,Ba=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ca=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)},Da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e},Ea=d.documentElement;!function(){var b,c,e,f,g=d.createElement("div"),h=d.createElement("div");if(h.style){h.style.backgroundClip="content-box",h.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===h.style.backgroundClip,g.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",g.appendChild(h);function i(){h.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",h.innerHTML="",Ea.appendChild(g);var d=a.getComputedStyle(h);b="1%"!==d.top,f="2px"===d.marginLeft,c="4px"===d.width,h.style.marginRight="50%",e="4px"===d.marginRight,Ea.removeChild(g)}n.extend(l,{pixelPosition:function(){return i(),b},boxSizingReliable:function(){return null==c&&i(),c},pixelMarginRight:function(){return null==c&&i(),e},reliableMarginLeft:function(){return null==c&&i(),f},reliableMarginRight:function(){var b,c=h.appendChild(d.createElement("div"));return c.style.cssText=h.style.cssText="-webkit-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",h.style.width="1px",Ea.appendChild(g),b=!parseFloat(a.getComputedStyle(c).marginRight),Ea.removeChild(g),h.removeChild(c),b}})}}();function Fa(a,b,c){var d,e,f,g,h=a.style;return c=c||Ca(a),g=c?c.getPropertyValue(b)||c[b]:void 0,""!==g&&void 0!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),c&&!l.pixelMarginRight()&&Ba.test(g)&&Aa.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f),void 0!==g?g+"":g}function Ga(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Ha=/^(none|table(?!-c[ea]).+)/,Ia={position:"absolute",visibility:"hidden",display:"block"},Ja={letterSpacing:"0",fontWeight:"400"},Ka=["Webkit","O","Moz","ms"],La=d.createElement("div").style;function Ma(a){if(a in La)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ka.length;while(c--)if(a=Ka[c]+b,a in La)return a}function Na(a,b,c){var d=T.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Oa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+U[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+U[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+U[f]+"Width",!0,e))):(g+=n.css(a,"padding"+U[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+U[f]+"Width",!0,e)));return g}function Pa(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ca(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Fa(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ba.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Oa(a,b,c||(g?"border":"content"),d,f)+"px"}function Qa(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=N.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&V(d)&&(f[g]=N.access(d,"olddisplay",za(d.nodeName)))):(e=V(d),"none"===c&&e||N.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Fa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=T.exec(c))&&e[1]&&(c=W(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(n.cssNumber[h]?"":"px")),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Fa(a,b,d)),"normal"===e&&b in Ja&&(e=Ja[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?Ha.test(n.css(a,"display"))&&0===a.offsetWidth?Da(a,Ia,function(){return Pa(a,b,d)}):Pa(a,b,d):void 0},set:function(a,c,d){var e,f=d&&Ca(a),g=d&&Oa(a,b,d,"border-box"===n.css(a,"boxSizing",!1,f),f);return g&&(e=T.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=n.css(a,b)),Na(a,c,g)}}}),n.cssHooks.marginLeft=Ga(l.reliableMarginLeft,function(a,b){return b?(parseFloat(Fa(a,"marginLeft"))||a.getBoundingClientRect().left-Da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px":void 0}),n.cssHooks.marginRight=Ga(l.reliableMarginRight,function(a,b){return b?Da(a,{display:"inline-block"},Fa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+U[d]+b]=f[d]||f[d-2]||f[0];return e}},Aa.test(a)||(n.cssHooks[a+b].set=Na)}),n.fn.extend({css:function(a,b){return K(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ca(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Qa(this,!0)},hide:function(){return Qa(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){V(this)?n(this).show():n(this).hide()})}});function Ra(a,b,c,d,e){return new Ra.prototype.init(a,b,c,d,e)}n.Tween=Ra,Ra.prototype={constructor:Ra,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ra.propHooks[this.prop];return a&&a.get?a.get(this):Ra.propHooks._default.get(this)},run:function(a){var b,c=Ra.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ra.propHooks._default.set(this),this}},Ra.prototype.init.prototype=Ra.prototype,Ra.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},Ra.propHooks.scrollTop=Ra.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=Ra.prototype.init,n.fx.step={};var Sa,Ta,Ua=/^(?:toggle|show|hide)$/,Va=/queueHooks$/;function Wa(){return a.setTimeout(function(){Sa=void 0}),Sa=n.now()}function Xa(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=U[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ya(a,b,c){for(var d,e=(_a.tweeners[b]||[]).concat(_a.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Za(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&V(a),q=N.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?N.get(a,"olddisplay")||za(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Ua.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?za(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=N.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;N.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ya(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function $a(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function _a(a,b,c){var d,e,f=0,g=_a.prefilters.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Sa||Wa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{},easing:n.easing._default},c),originalProperties:b,originalOptions:c,startTime:Sa||Wa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for($a(k,j.opts.specialEasing);g>f;f++)if(d=_a.prefilters[f].call(j,a,k,j.opts))return n.isFunction(d.stop)&&(n._queueHooks(j.elem,j.opts.queue).stop=n.proxy(d.stop,d)),d;return n.map(k,Ya,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(_a,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return W(c.elem,a,T.exec(b),c),c}]},tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.match(G);for(var c,d=0,e=a.length;e>d;d++)c=a[d],_a.tweeners[c]=_a.tweeners[c]||[],_a.tweeners[c].unshift(b)},prefilters:[Za],prefilter:function(a,b){b?_a.prefilters.unshift(a):_a.prefilters.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(V).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=_a(this,n.extend({},a),f);(e||N.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=N.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Va.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=N.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Xa(b,!0),a,d,e)}}),n.each({slideDown:Xa("show"),slideUp:Xa("hide"),slideToggle:Xa("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Sa=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Sa=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ta||(Ta=a.setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){a.clearInterval(Ta),Ta=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(b,c){return b=n.fx?n.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=d.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var ab,bb=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return K(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),e=n.attrHooks[b]||(n.expr.match.bool.test(b)?ab:void 0)),void 0!==c?null===c?void n.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=n.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(G);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)}}),ab={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=bb[b]||n.find.attr;bb[b]=function(a,b,d){var e,f;return d||(f=bb[b],bb[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,bb[b]=f),e}});var cb=/^(?:input|select|textarea|button)$/i,db=/^(?:a|area)$/i;n.fn.extend({prop:function(a,b){return K(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&n.isXMLDoc(a)||(b=n.propFix[b]||b,e=n.propHooks[b]),
3 void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):cb.test(a.nodeName)||db.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var eb=/[\t\r\n\f]/g;function fb(a){return a.getAttribute&&a.getAttribute("class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,fb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,fb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,fb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=fb(this),b&&N.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":N.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+fb(c)+" ").replace(eb," ").indexOf(b)>-1)return!0;return!1}});var gb=/\r/g,hb=/[\x20\t\r\n\f]+/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(gb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a)).replace(hb," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(n.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var ib=/^(?:focusinfocus|focusoutblur)$/;n.extend(n.event,{trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!ib.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),l=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},f||!o.trigger||o.trigger.apply(e,c)!==!1)){if(!f&&!o.noBubble&&!n.isWindow(e)){for(j=o.delegateType||q,ib.test(j+q)||(h=h.parentNode);h;h=h.parentNode)p.push(h),i=h;i===(e.ownerDocument||d)&&p.push(i.defaultView||i.parentWindow||a)}g=0;while((h=p[g++])&&!b.isPropagationStopped())b.type=g>1?j:o.bindType||q,m=(N.get(h,"events")||{})[b.type]&&N.get(h,"handle"),m&&m.apply(h,c),m=l&&h[l],m&&m.apply&&L(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=q,f||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!L(e)||l&&n.isFunction(e[q])&&!n.isWindow(e)&&(i=e[l],i&&(e[l]=null),n.event.triggered=q,e[q](),n.event.triggered=void 0,i&&(e[l]=i)),b.result}},simulate:function(a,b,c){var d=n.extend(new n.Event,c,{type:a,isSimulated:!0});n.event.trigger(d,null,b)}}),n.fn.extend({trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),l.focusin="onfocusin"in a,l.focusin||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a))};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=N.access(d,b);e||d.addEventListener(a,c,!0),N.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=N.access(d,b)-1;e?N.access(d,b,e):(d.removeEventListener(a,c,!0),N.remove(d,b))}}});var jb=a.location,kb=n.now(),lb=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||n.error("Invalid XML: "+b),c};var mb=/#.*$/,nb=/([?&])_=[^&]*/,ob=/^(.*?):[ \t]*([^\r\n]*)$/gm,pb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,qb=/^(?:GET|HEAD)$/,rb=/^\/\//,sb={},tb={},ub="*/".concat("*"),vb=d.createElement("a");vb.href=jb.href;function wb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(G)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function xb(a,b,c,d){var e={},f=a===tb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function yb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function zb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Ab(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:jb.href,type:"GET",isLocal:pb.test(jb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":ub,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?yb(yb(a,n.ajaxSettings),b):yb(n.ajaxSettings,a)},ajaxPrefilter:wb(sb),ajaxTransport:wb(tb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m=n.ajaxSetup({},c),o=m.context||m,p=m.context&&(o.nodeType||o.jquery)?n(o):n.event,q=n.Deferred(),r=n.Callbacks("once memory"),s=m.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,getResponseHeader:function(a){var b;if(2===v){if(!h){h={};while(b=ob.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===v?g:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return v||(a=u[c]=u[c]||a,t[a]=b),this},overrideMimeType:function(a){return v||(m.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>v)for(b in a)s[b]=[s[b],a[b]];else x.always(a[x.status]);return this},abort:function(a){var b=a||w;return e&&e.abort(b),z(0,b),this}};if(q.promise(x).complete=r.add,x.success=x.done,x.error=x.fail,m.url=((b||m.url||jb.href)+"").replace(mb,"").replace(rb,jb.protocol+"//"),m.type=c.method||c.type||m.method||m.type,m.dataTypes=n.trim(m.dataType||"*").toLowerCase().match(G)||[""],null==m.crossDomain){j=d.createElement("a");try{j.href=m.url,j.href=j.href,m.crossDomain=vb.protocol+"//"+vb.host!=j.protocol+"//"+j.host}catch(y){m.crossDomain=!0}}if(m.data&&m.processData&&"string"!=typeof m.data&&(m.data=n.param(m.data,m.traditional)),xb(sb,m,c,x),2===v)return x;k=n.event&&m.global,k&&0===n.active++&&n.event.trigger("ajaxStart"),m.type=m.type.toUpperCase(),m.hasContent=!qb.test(m.type),f=m.url,m.hasContent||(m.data&&(f=m.url+=(lb.test(f)?"&":"?")+m.data,delete m.data),m.cache===!1&&(m.url=nb.test(f)?f.replace(nb,"$1_="+kb++):f+(lb.test(f)?"&":"?")+"_="+kb++)),m.ifModified&&(n.lastModified[f]&&x.setRequestHeader("If-Modified-Since",n.lastModified[f]),n.etag[f]&&x.setRequestHeader("If-None-Match",n.etag[f])),(m.data&&m.hasContent&&m.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",m.contentType),x.setRequestHeader("Accept",m.dataTypes[0]&&m.accepts[m.dataTypes[0]]?m.accepts[m.dataTypes[0]]+("*"!==m.dataTypes[0]?", "+ub+"; q=0.01":""):m.accepts["*"]);for(l in m.headers)x.setRequestHeader(l,m.headers[l]);if(m.beforeSend&&(m.beforeSend.call(o,x,m)===!1||2===v))return x.abort();w="abort";for(l in{success:1,error:1,complete:1})x[l](m[l]);if(e=xb(tb,m,c,x)){if(x.readyState=1,k&&p.trigger("ajaxSend",[x,m]),2===v)return x;m.async&&m.timeout>0&&(i=a.setTimeout(function(){x.abort("timeout")},m.timeout));try{v=1,e.send(t,z)}catch(y){if(!(2>v))throw y;z(-1,y)}}else z(-1,"No Transport");function z(b,c,d,h){var j,l,t,u,w,y=c;2!==v&&(v=2,i&&a.clearTimeout(i),e=void 0,g=h||"",x.readyState=b>0?4:0,j=b>=200&&300>b||304===b,d&&(u=zb(m,x,d)),u=Ab(m,u,x,j),j?(m.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(n.lastModified[f]=w),w=x.getResponseHeader("etag"),w&&(n.etag[f]=w)),204===b||"HEAD"===m.type?y="nocontent":304===b?y="notmodified":(y=u.state,l=u.data,t=u.error,j=!t)):(t=y,!b&&y||(y="error",0>b&&(b=0))),x.status=b,x.statusText=(c||y)+"",j?q.resolveWith(o,[l,y,x]):q.rejectWith(o,[x,y,t]),x.statusCode(s),s=void 0,k&&p.trigger(j?"ajaxSuccess":"ajaxError",[x,m,j?l:t]),r.fireWith(o,[x,y]),k&&(p.trigger("ajaxComplete",[x,m]),--n.active||n.event.trigger("ajaxStop")))}return x},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax(n.extend({url:a,type:b,dataType:e,data:c,success:d},n.isPlainObject(a)&&a))}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return n.isFunction(a)?this.each(function(b){n(this).wrapInner(a.call(this,b))}):this.each(function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return!n.expr.filters.visible(a)},n.expr.filters.visible=function(a){return a.offsetWidth>0||a.offsetHeight>0||a.getClientRects().length>0};var Bb=/%20/g,Cb=/\[\]$/,Db=/\r?\n/g,Eb=/^(?:submit|button|image|reset|file)$/i,Fb=/^(?:input|select|textarea|keygen)/i;function Gb(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||Cb.test(a)?d(a,e):Gb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Gb(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Gb(c,a[c],b,e);return d.join("&").replace(Bb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Fb.test(this.nodeName)&&!Eb.test(a)&&(this.checked||!X.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(Db,"\r\n")}}):{name:b.name,value:c.replace(Db,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Hb={0:200,1223:204},Ib=n.ajaxSettings.xhr();l.cors=!!Ib&&"withCredentials"in Ib,l.ajax=Ib=!!Ib,n.ajaxTransport(function(b){var c,d;return l.cors||Ib&&!b.crossDomain?{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Hb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=n("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Jb=[],Kb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Jb.pop()||n.expando+"_"+kb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Kb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Kb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Kb,"$1"+e):b.jsonp!==!1&&(b.url+=(lb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?n(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Jb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||d;var e=x.exec(a),f=!c&&[];return e?[b.createElement(e[1])]:(e=ca([a],b,f),f&&f.length&&n(f).remove(),n.merge([],e.childNodes))};var Lb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Lb)return Lb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};function Mb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,n.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(e=d.getBoundingClientRect(),c=Mb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ea})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;n.fn[a]=function(d){return K(this,function(a,d,e){var f=Mb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Ga(l.pixelPosition,function(a,c){return c?(c=Fa(a,b),Ba.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return K(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)},size:function(){return this.length}}),n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Nb=a.jQuery,Ob=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Ob),b&&a.jQuery===n&&(a.jQuery=Nb),n},b||(a.jQuery=a.$=n),n});
+0
-23
examples/revelapp/tests/apptest.go less more
0 package tests
1
2 import (
3 "github.com/revel/revel/testing"
4 )
5
6 type AppTest struct {
7 testing.TestSuite
8 }
9
10 func (t *AppTest) Before() {
11 println("Set up")
12 }
13
14 func (t *AppTest) TestThatIndexPageWorks() {
15 t.Get("/")
16 t.AssertOk()
17 t.AssertContentType("text/html; charset=utf-8")
18 }
19
20 func (t *AppTest) After() {
21 println("Tear down")
22 }
44 "fmt"
55 "sync"
66
7 "github.com/bugsnag/bugsnag-go"
7 "github.com/bugsnag/bugsnag-go/v2"
88 )
99
1010 // Insert your API key
+0
-34
features/README.md less more
0 # Bugsnag-Go Maze-Runner tests
1
2 These are feature tests, built on top of [maze-runner](https://github.com/bugsnag/maze-runner) - a Cucumber wrapper with convenience steps for testing Bugsnag notifiers.
3
4 In order to run these tests locally you will need a Unix shell, Docker (and docker-compose) and Bundle installed.
5
6 You can then run all the tests locally using the `run-maze.sh` script located in this directory from the root of the repository.
7
8 ```bash
9 features/run-maze.sh
10 ```
11
12 ## Running specific features
13
14 You can run the maze-tests on a feature-by-feature basis too.
15
16 To run only a single feature you can do the following from the root of the repository.
17
18 ```bash
19 bundle install #only needs to be done once
20 # Only run the appversion feature for negroni
21 GO_VERSION=1.11 NEGRONI_VERSION=v1.0.0 bundle exec bugsnag-maze-runner features/negroni_features/appversion.feature
22 ```
23
24 Note that you will have to specify the Go and framework versions for each call.
25 Also note that when testing revel you'll have to specify both the `REVEL_VERSION` and `REVEL_CMD_VERSION`.
26 For martini we only support version 1.0, so no `MARTINI_VERSION` variable needs to be set.
27
28 Similarly, you can also run the tests for one framework:
29
30 ```bash
31 # Only run the negroni features
32 GO_VERSION=1.11 NEGRONI_VERSION=v1.0.0 bundle exec bugsnag-maze-runner features/negroni_features
33 ```
0 Feature: Configuring app type
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_TYPE" to "background-queue"
6 And I have built the service "app"
7
8 Scenario: An error report contains the configured app type when running a go app
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I run the go service "app" with the test case "handled"
11 Then I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "app.type" equals "background-queue"
14
15 Scenario: An session report contains the configured app type when running a go app
16 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
17 When I run the go service "app" with the test case "session"
18 Then I wait to receive a request after the start up session
19 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the payload field "app.type" equals "background-queue"
21
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I have built the service "app"
7
8 Scenario: An error report contains the configured app type when running a go app
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I run the go service "app" with the test case "handled"
11 Then I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "app.version" equals "3.1.2"
14
15 Scenario: An session report contains the configured app type when running a go app
16 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
17 When I run the go service "app" with the test case "session"
18 Then I wait to receive a request after the start up session
19 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the payload field "app.version" equals "3.1.2"
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
8 When I run the go service "app" with the test case "autonotify"
9 Then I wait for 3 seconds
10 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11 And the exception "errorClass" equals "*errors.errorString" for request 1
12 And the exception "message" equals "Go routine killed with auto notify" for request 1
0 Feature: Configure integration with environment variables
1
2 The library should be configurable using environment variables to support
3 single-line and reusable configuration
4
5 Background:
6 Given I set environment variable "BUGSNAG_API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
7 And I set environment variable "BUGSNAG_NOTIFY_ENDPOINT" to the notify endpoint
8 And I set environment variable "BUGSNAG_SESSIONS_ENDPOINT" to the sessions endpoint
9 And I have built the service "autoconfigure"
10
11 Scenario Outline: Adding content to handled events through env variables
12 Given I set environment variable "<variable>" to "<value>"
13 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
14 When I run the go service "autoconfigure" with the test case "<testcase>"
15 Then I wait to receive a request
16 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the event "<field>" equals "<value>"
18
19 Examples:
20 | testcase | variable | value | field |
21 | panic | BUGSNAG_APP_VERSION | 1.4.34 | app.version |
22 | panic | BUGSNAG_APP_TYPE | mailer-daemon | app.type |
23 | panic | BUGSNAG_RELEASE_STAGE | beta1 | app.releaseStage |
24 | panic | BUGSNAG_HOSTNAME | dream-machine-2 | device.hostname |
25 | panic | BUGSNAG_METADATA_device_instance | kube2-33-A | metaData.device.instance |
26 | panic | BUGSNAG_METADATA_framework_version | v3.1.0 | metaData.framework.version |
27 | panic | BUGSNAG_METADATA_device_runtime_level | 1C | metaData.device.runtime_level |
28 | panic | BUGSNAG_METADATA_Carrot | orange | metaData.custom.Carrot |
29
30 | handled | BUGSNAG_APP_VERSION | 1.4.34 | app.version |
31 | handled | BUGSNAG_APP_TYPE | mailer-daemon | app.type |
32 | handled | BUGSNAG_RELEASE_STAGE | beta1 | app.releaseStage |
33 | handled | BUGSNAG_HOSTNAME | dream-machine-2 | device.hostname |
34 | handled | BUGSNAG_METADATA_device_instance | kube2-33-A | metaData.device.instance |
35 | handled | BUGSNAG_METADATA_framework_version | v3.1.0 | metaData.framework.version |
36 | handled | BUGSNAG_METADATA_device_runtime_level | 1C | metaData.device.runtime_level |
37 | handled | BUGSNAG_METADATA_Carrot | orange | metaData.custom.Carrot |
38
39 Scenario: Configuring project packages
40 Given I set environment variable "BUGSNAG_PROJECT_PACKAGES" to "main,test"
41 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
42 When I run the go service "autoconfigure" with the test case "panic"
43 Then I wait to receive a request
44 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
45 And the in-project frames of the stacktrace are:
46 | file | method | lineNumber |
47 | cases.go | explicitPanic | 22 |
48 | main.go | main | 11 |
49
50 Scenario: Configuring source root
51 Given I set environment variable "BUGSNAG_SOURCE_ROOT" to the app directory
52 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
53 And I run the go service "autoconfigure" with the test case "panic"
54 Then I wait to receive a request
55 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
56 And the in-project frames of the stacktrace are:
57 | file | method | lineNumber |
58 | cases.go | explicitPanic | 22 |
59 | main.go | main | 11 |
60
61 Scenario: Delivering events filtering through notify release stages
62 Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "prod,beta"
63 And I set environment variable "BUGSNAG_RELEASE_STAGE" to "beta"
64 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
65 And I run the go service "autoconfigure" with the test case "panic"
66 Then I wait to receive a request
67 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
68
69 Scenario: Suppressing events through notify release stages
70 Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGES" to "prod,beta"
71 And I set environment variable "BUGSNAG_RELEASE_STAGE" to "dev"
72 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
73 And I run the go service "autoconfigure" with the test case "panic"
74 Then 0 requests were received
75
76 Scenario: Suppressing events using panic handler
77 Given I set environment variable "BUGSNAG_DISABLE_PANIC_HANDLER" to "1"
78 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
79 And I run the go service "autoconfigure" with the test case "panic"
80 And I wait for 2 seconds
81 Then 0 requests were received
82
83 Scenario: Enabling synchronous event delivery
84 Given I set environment variable "BUGSNAG_SYNCHRONOUS" to "1"
85 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
86 When I run the go service "autoconfigure" with the test case "handled"
87 Then 1 request was received
88
89 Scenario: Filtering metadata
90 Given I set environment variable "BUGSNAG_PARAMS_FILTERS" to "tomato,pears"
91 And I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "0"
92 When I run the go service "autoconfigure" with the test case "handled-metadata"
93 Then I wait to receive a request
94 And the event "metaData.fruit.Tomato" equals "[FILTERED]"
95 And the event "metaData.snacks.Carrot" equals "4"
0 Feature: Configuring endpoint
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report is sent successfully using the notify endpoint only
8 When I run the go service "app" with the test case "endpoint-notify"
9 Then I wait to receive a request
10 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11
12 Scenario: Configuring Bugsnag will panic if the sessions endpoint is configured without the notify endpoint
13 When I run the go service "app" with the test case "endpoint-session"
14 And I wait for 3 second
15 Then I should receive no requests
00 ARG GO_VERSION
11 FROM golang:${GO_VERSION}-alpine
22
3 RUN apk update && \
4 apk upgrade && \
5 apk add git
3 RUN apk update && apk upgrade && apk add git bash
64
75 ENV GOPATH /app
86
97 COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
10 WORKDIR /app/src/github.com/bugsnag/bugsnag-go
8 WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2
119
12 RUN go get . ./sessions ./headers ./errors
10 RUN go get ./...
1311
1412 # Copy test scenarios
1513 COPY ./app /app/src/test
1614 WORKDIR /app/src/test
15
16 # Ensure subsequent steps are re-run if the GO_VERSION variable changes
17 ARG GO_VERSION
18 # Create app module - avoid locking bugsnag dep by not checking it in
19 # Skip on old versions of Go which pre-date modules
20 RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \
21 go mod init && go mod tidy; \
22 fi
23
24 RUN chmod +x run.sh
25 CMD ["/app/src/test/run.sh"]
55 "fmt"
66 "log"
77 "os"
8 "runtime"
89 "strconv"
910 "strings"
1011 "time"
1112
12 bugsnag "github.com/bugsnag/bugsnag-go"
13 bugsnag "github.com/bugsnag/bugsnag-go/v2"
1314 )
1415
1516 func configureBasicBugsnag(testcase string) {
4344 }
4445
4546 switch testcase {
46 case "endpoint legacy":
47 config.Endpoint = os.Getenv("BUGSNAG_ENDPOINT")
48 case "endpoint notify":
47 case "endpoint-notify":
4948 config.Endpoints = bugsnag.Endpoints{Notify: os.Getenv("BUGSNAG_ENDPOINT")}
50 case "endpoint session":
49 case "endpoint-session":
5150 config.Endpoints = bugsnag.Endpoints{Sessions: os.Getenv("BUGSNAG_ENDPOINT")}
5251 default:
5352 config.Endpoints = bugsnag.Endpoints{
7372 switch *test {
7473 case "unhandled":
7574 unhandledCrash()
76 case "handled", "endpoint legacy", "endpoint notify", "endpoint session":
75 case "handled", "endpoint-legacy", "endpoint-notify", "endpoint-session":
7776 handledError()
78 case "handled with callback":
77 case "handled-with-callback":
7978 handledCallbackError()
8079 case "session":
8180 session()
8988 filtered()
9089 case "recover":
9190 dontDie()
92 case "session and error":
91 case "session-and-error":
9392 sessionAndError()
94 case "send and exit":
93 case "send-and-exit":
9594 sendAndExit()
9695 case "user":
9796 user()
98 case "multiple handled":
97 case "multiple-handled":
9998 multipleHandled()
100 case "multiple unhandled":
99 case "multiple-unhandled":
101100 multipleUnhandled()
102 case "make unhandled with callback":
101 case "make-unhandled-with-callback":
103102 handledToUnhandled()
103 case "nested-error":
104 nestedHandledError()
104105 default:
105106 log.Println("Not a valid test flag: " + *test)
106107 os.Exit(1)
255256 // Give some time for the error to be sent before exiting
256257 time.Sleep(200 * time.Millisecond)
257258 }
259
260 type customErr struct {
261 msg string
262 cause error
263 callers []uintptr
264 }
265
266 func newCustomErr(msg string, cause error) error {
267 callers := make([]uintptr, 8)
268 runtime.Callers(2, callers)
269 return customErr {
270 msg: msg,
271 cause: cause,
272 callers: callers,
273 }
274 }
275
276 func (err customErr) Error() string {
277 return err.msg
278 }
279
280 func (err customErr) Unwrap() error {
281 return err.cause
282 }
283
284 func (err customErr) Callers() []uintptr {
285 return err.callers
286 }
287
288 func nestedHandledError() {
289 if err := login("token " + os.Getenv("API_KEY")); err != nil {
290 bugsnag.Notify(newCustomErr("terminate process", err))
291 // Give some time for the error to be sent before exiting
292 time.Sleep(200 * time.Millisecond)
293 } else {
294 i := len(os.Getenv("API_KEY"))
295 // Some nonsense to avoid inlining checkValue
296 if val, err := checkValue(i); err != nil {
297 fmt.Printf("err: %v, val: %d", err, val)
298 }
299 if val, err := checkValue(i-46); err != nil {
300 fmt.Printf("err: %v, val: %d", err, val)
301 }
302
303 log.Fatalf("This test is broken - no error was generated.")
304 }
305 }
306
307 func login(token string) error {
308 val, err := checkValue(len(token) * -1)
309 if err != nil {
310 return newCustomErr("login failed", err)
311 }
312 fmt.Printf("val: %d", val)
313 return nil
314 }
315
316 func checkValue(i int) (int, error) {
317 if i < 0 {
318 return 0, newCustomErr("invalid token", nil)
319 } else if i % 2 == 0 {
320 return i / 2, nil
321 } else if i < 9 {
322 return i * 3, nil
323 }
324
325 return i * 4, nil
326 }
0 #!/usr/bin/env bash
1
2 # SIGTERM or SIGINT trapped (likely SIGTERM from docker), pass it onto app
3 # process
4 function _term_or_init {
5 kill -TERM "$APP_PID" 2>/dev/null
6 wait $APP_PID
7 }
8
9 # The bugsnag notifier monitor process needs at least 300ms, in order to ensure
10 # that it can send its notify
11 function _exit {
12 sleep 1
13 }
14
15 trap _term_or_init SIGTERM SIGINT
16 trap _exit EXIT
17
18 PROC="${@:1}"
19 $PROC &
20
21 # Wait on the app process to ensure that this script is able to trap the SIGTERM
22 # signal
23 APP_PID=$!
24 wait $APP_PID
0 ARG GO_VERSION
1 FROM golang:${GO_VERSION}-alpine
2
3 RUN apk update && apk upgrade && apk add git bash
4
5 ENV GOPATH /app
6
7 COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
8 WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2
9
10 # Get bugsnag dependencies
11 RUN go get ./...
12
13 # Copy test scenarios
14 COPY ./autoconfigure /app/src/test
15 WORKDIR /app/src/test
16
17 # Ensure subsequent steps are re-run if the GO_VERSION variable changes
18 ARG GO_VERSION
19 # Create app module - avoid locking bugsnag dep by not checking it in
20 # Skip on old versions of Go which pre-date modules
21 RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \
22 go mod init && go mod tidy; \
23 fi
24
25 RUN chmod +x run.sh
26 CMD ["/app/src/test/run.sh"]
0 package main
1
2 import (
3 "fmt"
4
5 "github.com/bugsnag/bugsnag-go/v2"
6 )
7
8 func explicitPanic() {
9 panic("PANIQ!")
10 }
11
12 func handledEvent() {
13 bugsnag.Notify(fmt.Errorf("gone awry!"))
14 }
15
16 func handledMetadata() {
17 bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error {
18 event.MetaData.Add("fruit", "Tomato", "beefsteak")
19 event.MetaData.Add("snacks", "Carrot", "4")
20 return nil
21 })
22 handledEvent()
23 }
0 package main
1
2 import (
3 "flag"
4 "fmt"
5 "time"
6
7 "github.com/bugsnag/bugsnag-go/v2"
8 )
9
10 var testcase = flag.String("test", "", "the error scenario to run")
11
12 func main() {
13 bugsnag.Configure(bugsnag.Configuration{})
14
15 // Increase publish rate for testing
16 bugsnag.DefaultSessionPublishInterval = time.Millisecond * 50
17
18 flag.Parse()
19
20 switch *testcase {
21 case "panic":
22 explicitPanic()
23 case "handled":
24 handledEvent()
25 case "handled-metadata":
26 handledMetadata()
27 case "no-op":
28 // nothing to see here
29 default:
30 fmt.Printf("No test case found for '%s'\n", *testcase)
31 }
32 time.Sleep(time.Millisecond * 100) // time to send before termination
33 }
0 #!/usr/bin/env bash
1
2 # SIGTERM or SIGINT trapped (likely SIGTERM from docker), pass it onto app
3 # process
4 function _term_or_init {
5 kill -TERM "$APP_PID" 2>/dev/null
6 wait $APP_PID
7 }
8
9 # The bugsnag notifier monitor process needs at least 300ms, in order to ensure
10 # that it can send its notify
11 function _exit {
12 sleep 1
13 }
14
15 trap _term_or_init SIGTERM SIGINT
16 trap _exit EXIT
17
18 PROC="${@:1}"
19 $PROC &
20
21 # Wait on the app process to ensure that this script is able to trap the SIGTERM
22 # signal
23 APP_PID=$!
24 wait $APP_PID
1919 - SYNCHRONOUS
2020 - SERVER_PORT
2121 restart: "no"
22
23 autoconfigure:
24 build:
25 context: .
26 dockerfile: autoconfigure/Dockerfile
27 args:
28 - GO_VERSION
29 environment:
30 - BUGSNAG_API_KEY
31 - BUGSNAG_APP_TYPE
32 - BUGSNAG_APP_VERSION
33 - BUGSNAG_AUTO_CAPTURE_SESSIONS
34 - BUGSNAG_DISABLE_PANIC_HANDLER
35 - BUGSNAG_HOSTNAME
36 - BUGSNAG_NOTIFY_ENDPOINT
37 - BUGSNAG_NOTIFY_RELEASE_STAGES
38 - BUGSNAG_PARAMS_FILTERS
39 - BUGSNAG_PROJECT_PACKAGES
40 - BUGSNAG_RELEASE_STAGE
41 - BUGSNAG_SESSIONS_ENDPOINT
42 - BUGSNAG_SOURCE_ROOT
43 - BUGSNAG_SYNCHRONOUS
44 - BUGSNAG_METADATA_Carrot
45 - BUGSNAG_METADATA_device_instance
46 - BUGSNAG_METADATA_device_runtime_level
47 - BUGSNAG_METADATA_framework_version
48 - BUGSNAG_METADATA_fruit_Tomato
49 - BUGSNAG_METADATA_snacks_Carrot
50 restart: "no"
51 command: go run .
2252
2353 nethttp:
2454 build:
+0
-28
features/fixtures/gin/Dockerfile less more
0 ARG GO_VERSION
1 FROM golang:${GO_VERSION}-alpine as notifier_builder
2
3 RUN apk update && \
4 apk upgrade && \
5 apk add git
6
7 ENV GOPATH /app
8
9 COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
10 WORKDIR /app/src/github.com/bugsnag/bugsnag-go
11
12 RUN go get -v -d . ./sessions ./headers ./errors ./gin
13
14 FROM notifier_builder
15
16 # Switch to correct version of gin
17 ARG GIN_VERSION
18 RUN test -n "$GIN_VERSION"
19
20 RUN (cd /app/src/github.com/gin-gonic/gin && git checkout $GIN_VERSION)
21 RUN go get -v -d . ./sessions ./headers ./errors ./gin
22 RUN (cd /app/src/github.com/gin-gonic/gin && go install)
23
24
25 # Copy test scenarios
26 COPY ./gin /app/src/test
27 WORKDIR /app/src/test
+0
-125
features/fixtures/gin/main.go less more
0 package main
1
2 import (
3 "context"
4 "fmt"
5 "log"
6 "os"
7 "strconv"
8 "strings"
9 "time"
10
11 bugsnag "github.com/bugsnag/bugsnag-go"
12 "github.com/gin-gonic/gin"
13
14 "github.com/bugsnag/bugsnag-go/gin"
15 )
16
17 func main() {
18 g := gin.Default()
19 config := bugsnag.Configuration{
20 APIKey: os.Getenv("API_KEY"),
21 Endpoints: bugsnag.Endpoints{
22 Notify: os.Getenv("BUGSNAG_ENDPOINT"),
23 Sessions: os.Getenv("BUGSNAG_ENDPOINT"),
24 },
25 AppVersion: os.Getenv("APP_VERSION"),
26 AppType: os.Getenv("APP_TYPE"),
27 Hostname: os.Getenv("HOSTNAME"),
28 }
29
30 if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" {
31 config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",")
32 }
33
34 if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" {
35 config.ReleaseStage = releaseStage
36 }
37
38 if filters := os.Getenv("PARAMS_FILTERS"); filters != "" {
39 config.ParamsFilters = []string{filters}
40 }
41
42 acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS"))
43 if err == nil {
44 config.AutoCaptureSessions = acs
45 }
46 bugsnag.Configure(config)
47
48 // Increase publish rate for testing
49 bugsnag.DefaultSessionPublishInterval = time.Millisecond * 300
50
51 g.Use(gin.Recovery(), bugsnaggin.AutoNotify(config))
52
53 g.GET("/autonotify-then-recover", unhandledCrash)
54 g.GET("/handled", handledError)
55 g.GET("/session", session)
56 g.GET("/autonotify", autonotify)
57 g.GET("/onbeforenotify", onBeforeNotify)
58 g.GET("/recover", dontDie)
59 g.GET("/user", user)
60 g.Run(":" + os.Getenv("SERVER_PORT"))
61
62 }
63
64 func unhandledCrash(c *gin.Context) {
65 // Invalid type assertion, will panic
66 func(a interface{}) string {
67 return a.(string)
68 }(struct{}{})
69 }
70
71 func handledError(c *gin.Context) {
72 if _, err := os.Open("nonexistent_file.txt"); err != nil {
73 if errClass := os.Getenv("ERROR_CLASS"); errClass != "" {
74 bugsnag.Notify(err, c.Request.Context(), bugsnag.ErrorClass{Name: errClass})
75 } else {
76 bugsnag.Notify(err, c.Request.Context())
77 }
78 }
79 }
80
81 func session(c *gin.Context) {
82 log.Println("single session")
83 }
84
85 func dontDie(c *gin.Context) {
86 defer bugsnag.Recover(c.Request.Context())
87 panic("Request killed but recovered")
88 }
89
90 func user(c *gin.Context) {
91 bugsnag.Notify(fmt.Errorf("oops"), bugsnag.User{
92 Id: "test-user-id",
93 Name: "test-user-name",
94 Email: "test-user-email",
95 })
96 }
97
98 func onBeforeNotify(c *gin.Context) {
99 bugsnag.OnBeforeNotify(
100 func(event *bugsnag.Event, config *bugsnag.Configuration) error {
101 if event.Message == "Ignore this error" {
102 return fmt.Errorf("not sending errors to ignore")
103 }
104 // continue notifying as normal
105 if event.Message == "Change error message" {
106 event.Message = "Error message was changed"
107 }
108 return nil
109 })
110 bugsnag.Notify(fmt.Errorf("Ignore this error"))
111 time.Sleep(100 * time.Millisecond)
112 bugsnag.Notify(fmt.Errorf("Don't ignore this error"))
113 time.Sleep(100 * time.Millisecond)
114 bugsnag.Notify(fmt.Errorf("Change error message"))
115 time.Sleep(100 * time.Millisecond)
116 }
117
118 func autonotify(c *gin.Context) {
119 go func(ctx context.Context) {
120 defer func() { recover() }()
121 defer bugsnag.AutoNotify(ctx)
122 panic("Go routine killed with auto notify")
123 }(c.Request.Context())
124 }
+0
-17
features/fixtures/martini/Dockerfile less more
0 ARG GO_VERSION
1 FROM golang:${GO_VERSION}-alpine as notifier_builder
2
3 RUN apk update && \
4 apk upgrade && \
5 apk add git
6
7 ENV GOPATH /app
8
9 COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
10 WORKDIR /app/src/github.com/bugsnag/bugsnag-go
11
12 RUN go get -v -d . ./sessions ./headers ./errors ./martini
13
14 # Copy test scenarios
15 COPY ./martini /app/src/test
16 WORKDIR /app/src/test
+0
-124
features/fixtures/martini/main.go less more
0 package main
1
2 import (
3 "context"
4 "fmt"
5 "log"
6 "net/http"
7 "os"
8 "strconv"
9 "strings"
10 "time"
11
12 "github.com/bugsnag/bugsnag-go"
13 "github.com/bugsnag/bugsnag-go/martini"
14 "github.com/go-martini/martini"
15 )
16
17 func main() {
18 m := martini.Classic()
19 config := bugsnag.Configuration{
20 APIKey: os.Getenv("API_KEY"),
21 Endpoints: bugsnag.Endpoints{
22 Notify: os.Getenv("BUGSNAG_ENDPOINT"),
23 Sessions: os.Getenv("BUGSNAG_ENDPOINT"),
24 },
25 AppVersion: os.Getenv("APP_VERSION"),
26 AppType: os.Getenv("APP_TYPE"),
27 Hostname: os.Getenv("HOSTNAME"),
28 }
29
30 if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" {
31 config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",")
32 }
33
34 if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" {
35 config.ReleaseStage = releaseStage
36 }
37
38 if filters := os.Getenv("PARAMS_FILTERS"); filters != "" {
39 config.ParamsFilters = []string{filters}
40 }
41
42 acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS"))
43 if err == nil {
44 config.AutoCaptureSessions = acs
45 }
46 bugsnag.Configure(config)
47
48 // Increase publish rate for testing
49 bugsnag.DefaultSessionPublishInterval = time.Millisecond * 300
50
51 m.Use(martini.Recovery())
52 m.Use(bugsnagmartini.AutoNotify())
53 m.Get("/autonotify-then-recover", unhandledCrash)
54 m.Get("/handled", handledError)
55 m.Get("/session", session)
56 m.Get("/autonotify", autonotify)
57 m.Get("/onbeforenotify", onBeforeNotify)
58 m.Get("/recover", dontDie)
59 m.Get("/user", user)
60 m.RunOnAddr(":" + os.Getenv("SERVER_PORT"))
61 }
62
63 func unhandledCrash() {
64 // Invalid type assertion, will panic
65 func(a interface{}) string {
66 return a.(string)
67 }(struct{}{})
68 }
69
70 func handledError(r *http.Request) {
71 if _, err := os.Open("nonexistent_file.txt"); err != nil {
72 if errClass := os.Getenv("ERROR_CLASS"); errClass != "" {
73 bugsnag.Notify(err, r.Context(), bugsnag.ErrorClass{Name: errClass})
74 } else {
75 bugsnag.Notify(err, r.Context())
76 }
77 }
78 }
79
80 func session() {
81 log.Println("single session")
82 }
83
84 func dontDie(r *http.Request) {
85 defer bugsnag.Recover(r.Context())
86 panic("Request killed but recovered")
87 }
88
89 func user() {
90 bugsnag.Notify(fmt.Errorf("oops"), bugsnag.User{
91 Id: "test-user-id",
92 Name: "test-user-name",
93 Email: "test-user-email",
94 })
95 }
96
97 func onBeforeNotify(r *http.Request) {
98 bugsnag.OnBeforeNotify(
99 func(event *bugsnag.Event, config *bugsnag.Configuration) error {
100 if event.Message == "Ignore this error" {
101 return fmt.Errorf("not sending errors to ignore")
102 }
103 // continue notifying as normal
104 if event.Message == "Change error message" {
105 event.Message = "Error message was changed"
106 }
107 return nil
108 })
109 bugsnag.Notify(fmt.Errorf("Ignore this error"))
110 time.Sleep(100 * time.Millisecond)
111 bugsnag.Notify(fmt.Errorf("Don't ignore this error"))
112 time.Sleep(100 * time.Millisecond)
113 bugsnag.Notify(fmt.Errorf("Change error message"))
114 time.Sleep(100 * time.Millisecond)
115 }
116
117 func autonotify(r *http.Request) {
118 go func(ctx context.Context) {
119 defer func() { recover() }()
120 defer bugsnag.AutoNotify(ctx)
121 panic("Go routine killed with auto notify")
122 }(r.Context())
123 }
+0
-28
features/fixtures/negroni/Dockerfile less more
0 ARG GO_VERSION
1 FROM golang:${GO_VERSION}-alpine as notifier_builder
2
3 RUN apk update && \
4 apk upgrade && \
5 apk add git
6
7 ENV GOPATH /app
8
9 COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
10 WORKDIR /app/src/github.com/bugsnag/bugsnag-go
11
12 RUN go get -v -d . ./sessions ./headers ./errors ./negroni
13
14 FROM notifier_builder
15
16 # Switch to correct version of negroni
17 ARG NEGRONI_VERSION
18 RUN test -n "$NEGRONI_VERSION"
19
20 RUN (cd /app/src/github.com/urfave/negroni && git checkout $NEGRONI_VERSION)
21 RUN go get -v -d . ./sessions ./headers ./errors ./negroni
22 RUN (cd /app/src/github.com/urfave/negroni && go install)
23
24
25 # Copy test scenarios
26 COPY ./negroni /app/src/test
27 WORKDIR /app/src/test
+0
-128
features/fixtures/negroni/main.go less more
0 package main
1
2 import (
3 "context"
4 "fmt"
5 "log"
6 "net/http"
7 "os"
8 "strconv"
9 "strings"
10 "time"
11
12 "github.com/bugsnag/bugsnag-go"
13 "github.com/bugsnag/bugsnag-go/negroni"
14 "github.com/urfave/negroni"
15 )
16
17 func main() {
18 config := bugsnag.Configuration{
19 APIKey: os.Getenv("API_KEY"),
20 Endpoints: bugsnag.Endpoints{
21 Notify: os.Getenv("BUGSNAG_ENDPOINT"),
22 Sessions: os.Getenv("BUGSNAG_ENDPOINT"),
23 },
24 AppVersion: os.Getenv("APP_VERSION"),
25 AppType: os.Getenv("APP_TYPE"),
26 Hostname: os.Getenv("HOSTNAME"),
27 }
28
29 if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" {
30 config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",")
31 }
32
33 if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" {
34 config.ReleaseStage = releaseStage
35 }
36
37 if filters := os.Getenv("PARAMS_FILTERS"); filters != "" {
38 config.ParamsFilters = []string{filters}
39 }
40
41 acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS"))
42 if err == nil {
43 config.AutoCaptureSessions = acs
44 }
45 bugsnag.Configure(config)
46
47 // Increase publish rate for testing
48 bugsnag.DefaultSessionPublishInterval = time.Millisecond * 300
49
50 mux := http.NewServeMux()
51 mux.HandleFunc("/autonotify-then-recover", unhandledCrash)
52 mux.HandleFunc("/handled", handledError)
53 mux.HandleFunc("/session", session)
54 mux.HandleFunc("/autonotify", autonotify)
55 mux.HandleFunc("/onbeforenotify", onBeforeNotify)
56 mux.HandleFunc("/recover", dontDie)
57 mux.HandleFunc("/user", user)
58
59 n := negroni.New()
60 n.Use(negroni.NewRecovery())
61 // Add bugsnag handler after negroni.NewRecovery() to ensure panics get picked up
62 n.Use(bugsnagnegroni.AutoNotify())
63 n.UseHandler(mux)
64 http.ListenAndServe(":"+os.Getenv("SERVER_PORT"), n)
65 }
66
67 func unhandledCrash(w http.ResponseWriter, r *http.Request) {
68 // Invalid type assertion, will panic
69 func(a interface{}) string {
70 return a.(string)
71 }(struct{}{})
72 }
73
74 func handledError(w http.ResponseWriter, r *http.Request) {
75 if _, err := os.Open("nonexistent_file.txt"); err != nil {
76 if errClass := os.Getenv("ERROR_CLASS"); errClass != "" {
77 bugsnag.Notify(err, r.Context(), bugsnag.ErrorClass{Name: errClass})
78 } else {
79 bugsnag.Notify(err, r.Context())
80 }
81 }
82 }
83
84 func session(w http.ResponseWriter, r *http.Request) {
85 log.Println("single session")
86 }
87
88 func dontDie(w http.ResponseWriter, r *http.Request) {
89 defer bugsnag.Recover(r.Context())
90 panic("Request killed but recovered")
91 }
92
93 func user(w http.ResponseWriter, r *http.Request) {
94 bugsnag.Notify(fmt.Errorf("oops"), bugsnag.User{
95 Id: "test-user-id",
96 Name: "test-user-name",
97 Email: "test-user-email",
98 })
99 }
100
101 func onBeforeNotify(w http.ResponseWriter, r *http.Request) {
102 bugsnag.OnBeforeNotify(
103 func(event *bugsnag.Event, config *bugsnag.Configuration) error {
104 if event.Message == "Ignore this error" {
105 return fmt.Errorf("not sending errors to ignore")
106 }
107 // continue notifying as normal
108 if event.Message == "Change error message" {
109 event.Message = "Error message was changed"
110 }
111 return nil
112 })
113 bugsnag.Notify(fmt.Errorf("Ignore this error"))
114 time.Sleep(100 * time.Millisecond)
115 bugsnag.Notify(fmt.Errorf("Don't ignore this error"))
116 time.Sleep(100 * time.Millisecond)
117 bugsnag.Notify(fmt.Errorf("Change error message"))
118 time.Sleep(100 * time.Millisecond)
119 }
120
121 func autonotify(w http.ResponseWriter, r *http.Request) {
122 go func(ctx context.Context) {
123 defer func() { recover() }()
124 defer bugsnag.AutoNotify(ctx)
125 panic("Go routine killed with auto notify")
126 }(r.Context())
127 }
77 ENV GOPATH /app
88
99 COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
10 WORKDIR /app/src/github.com/bugsnag/bugsnag-go
10 WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2
1111
12 RUN go get -v -d . ./sessions ./headers ./errors
12 # Get bugsnag dependencies
13 RUN go get ./...
1314
1415 # Copy test scenarios
1516 COPY ./net_http /app/src/test
1617 WORKDIR /app/src/test
18
19 # Ensure subsequent steps are re-run if the GO_VERSION variable changes
20 ARG GO_VERSION
21 # Create app module - avoid locking bugsnag dep by not checking it in
22 # Skip on old versions of Go which pre-date modules
23 RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \
24 go mod init && go mod tidy; \
25 fi
99 "strings"
1010 "time"
1111
12 bugsnag "github.com/bugsnag/bugsnag-go"
12 bugsnag "github.com/bugsnag/bugsnag-go/v2"
1313 )
1414
1515 func main() {
+0
-3
features/fixtures/revel/.gitignore less more
0 test-results/
1 tmp/
2 routes/
+0
-35
features/fixtures/revel/Dockerfile less more
0 ARG GO_VERSION
1 FROM golang:${GO_VERSION}-alpine as notifier_builder
2
3 RUN apk update && \
4 apk upgrade && \
5 apk add git
6
7 ENV GOPATH /app
8
9 COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
10 WORKDIR /app/src/github.com/bugsnag/bugsnag-go
11
12 RUN go get -v -d . ./sessions ./headers ./errors ./revel
13
14 FROM notifier_builder
15
16 # Switch to correct version of revel
17 ARG REVEL_VERSION
18 RUN test -n "$REVEL_VERSION"
19
20 ARG REVEL_CMD_VERSION
21 RUN test -n "$REVEL_CMD_VERSION"
22
23 RUN (cd /app/src/github.com/revel/revel && git checkout $REVEL_VERSION)
24 RUN (cd /app/src/github.com/revel/revel && go get -v -d ./...)
25 RUN (cd /app/src/github.com/revel/revel && go install)
26
27 RUN go get github.com/revel/cmd/revel
28 RUN (cd /app/src/github.com/revel/cmd/revel && git checkout $REVEL_CMD_VERSION)
29 RUN (cd /app/src/github.com/revel/cmd/revel && go get -v -d ./...)
30 RUN (cd /app/src/github.com/revel/cmd/revel && go install)
31
32 # Copy test scenarios
33 COPY ./revel /app/src/test
34 WORKDIR /app/src
+0
-43
features/fixtures/revel/README.md less more
0 # Welcome to Revel
1
2 A high-productivity web framework for the [Go language](http://www.golang.org/).
3
4
5 ### Start the web server:
6
7 revel run myapp
8
9 ### Go to http://localhost:9000/ and you'll see:
10
11 "It works"
12
13 ## Code Layout
14
15 The directory structure of a generated Revel application:
16
17 conf/ Configuration directory
18 app.conf Main app configuration file
19 routes Routes definition file
20
21 app/ App sources
22 init.go Interceptor registration
23 controllers/ App controllers go here
24 views/ Templates directory
25
26 messages/ Message files
27
28 public/ Public static assets
29 css/ CSS files
30 js/ Javascript files
31 images/ Image files
32
33 tests/ Test suites
34
35
36 ## Help
37
38 * The [Getting Started with Revel](http://revel.github.io/tutorial/gettingstarted.html).
39 * The [Revel guides](http://revel.github.io/manual/index.html).
40 * The [Revel sample apps](http://revel.github.io/examples/index.html).
41 * The [API documentation](https://godoc.org/github.com/revel/revel).
42
+0
-83
features/fixtures/revel/app/controllers/app.go less more
0 package controllers
1
2 import (
3 "fmt"
4 "os"
5
6 "github.com/bugsnag/bugsnag-go"
7 "github.com/revel/revel"
8 )
9
10 type App struct {
11 *revel.Controller
12 }
13
14 func (c App) Index() revel.Result {
15 return c.Render()
16 }
17
18 func (c App) Handled() revel.Result {
19 if _, err := os.Open("nonexistent_file.txt"); err != nil {
20 if errClass := os.Getenv("ERROR_CLASS"); errClass != "" {
21 bugsnag.Notify(err, c.Args["context"], bugsnag.ErrorClass{Name: errClass})
22 } else {
23 bugsnag.Notify(err, c.Args["context"])
24 }
25 }
26 return c.Render()
27 }
28
29 func (c App) Unhandled() revel.Result {
30 // Invalid type assertion, will panic
31 func(a interface{}) string {
32 return a.(string)
33 }(struct{}{})
34 return c.Render()
35 }
36
37 func (c App) Session() revel.Result {
38 return c.Render()
39 }
40
41 func (c App) AutoNotify() revel.Result {
42 go func(ctx interface{}) {
43 defer func() { recover() }()
44 defer bugsnag.AutoNotify(ctx)
45 panic("Go routine killed with auto notify")
46 }(c.Args["context"])
47 return c.Render()
48 }
49
50 func (c App) OnBeforeNotify() revel.Result {
51 bugsnag.OnBeforeNotify(
52 func(event *bugsnag.Event, config *bugsnag.Configuration) error {
53 if event.Message == "Ignore this error" {
54 return fmt.Errorf("not sending errors to ignore")
55 }
56 // continue notifying as normal
57 if event.Message == "Change error message" {
58 event.Message = "Error message was changed"
59 }
60 return nil
61 })
62
63 notifier := bugsnag.New()
64 notifier.NotifySync(fmt.Errorf("Don't ignore this error"), true)
65 notifier.NotifySync(fmt.Errorf("Ignore this error"), true)
66 notifier.NotifySync(fmt.Errorf("Change error message"), true)
67 return c.Render()
68 }
69
70 func (c App) Recover() revel.Result {
71 defer bugsnag.Recover(c.Args["context"])
72 panic("Request killed but recovered")
73 }
74
75 func (c App) User() revel.Result {
76 bugsnag.Notify(fmt.Errorf("oops"), bugsnag.User{
77 Id: "test-user-id",
78 Name: "test-user-name",
79 Email: "test-user-email",
80 })
81 return c.Render()
82 }
+0
-105
features/fixtures/revel/app/init.go less more
0 package app
1
2 import (
3 "log"
4 "os"
5 "strconv"
6 "strings"
7 "time"
8
9 bugsnag "github.com/bugsnag/bugsnag-go"
10 "github.com/bugsnag/bugsnag-go/revel"
11 "github.com/revel/revel"
12 )
13
14 var (
15 // AppVersion revel app version (ldflags)
16 AppVersion string
17
18 // BuildTime revel app build-time (ldflags)
19 BuildTime string
20 )
21
22 func init() {
23 // Filters is the default set of global filters.
24 revel.Filters = []revel.Filter{
25 revel.PanicFilter, // Recover from panics and display an error page instead.
26 bugsnagrevel.Filter,
27 revel.RouterFilter, // Use the routing table to select the right Action
28 revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
29 revel.ParamsFilter, // Parse parameters into Controller.Params.
30 revel.SessionFilter, // Restore and write the session cookie.
31 revel.FlashFilter, // Restore and write the flash cookie.
32 revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
33 revel.I18nFilter, // Resolve the requested language
34 HeaderFilter, // Add some security based headers
35 revel.InterceptorFilter, // Run interceptors around the action.
36 revel.CompressFilter, // Compress the result.
37 revel.BeforeAfterFilter, // Call the before and after filter functions
38 revel.ActionInvoker, // Invoke the action.
39 }
40
41 // Register startup functions with OnAppStart
42 // revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
43 // ( order dependent )
44 // revel.OnAppStart(ExampleStartupScript)
45 // revel.OnAppStart(InitDB)
46 // revel.OnAppStart(FillCache)
47
48 bugsnag.DefaultSessionPublishInterval = time.Millisecond * 300
49
50 if os.Getenv("USE_PROPERTIES_FILE_CONFIG") != "" {
51 return
52 }
53
54 log.Println("CONFIGURING")
55 config := bugsnag.Configuration{
56 APIKey: os.Getenv("API_KEY"),
57 Endpoints: bugsnag.Endpoints{
58 Notify: os.Getenv("BUGSNAG_ENDPOINT"),
59 Sessions: os.Getenv("BUGSNAG_ENDPOINT"),
60 },
61 AppVersion: os.Getenv("APP_VERSION"),
62 AppType: os.Getenv("APP_TYPE"),
63 Hostname: os.Getenv("HOSTNAME"),
64 }
65
66 if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" {
67 config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",")
68 }
69
70 if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" {
71 config.ReleaseStage = releaseStage
72 }
73
74 if filters := os.Getenv("PARAMS_FILTERS"); filters != "" {
75 config.ParamsFilters = []string{filters}
76 }
77
78 acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS"))
79 if err == nil {
80 log.Println("SETTING AUTO CAPTURE " + strconv.FormatBool(acs))
81 config.AutoCaptureSessions = acs
82 }
83 bugsnag.Configure(config)
84 }
85
86 // HeaderFilter adds common security headers
87 // There is a full implementation of a CSRF filter in
88 // https://github.com/revel/modules/tree/master/csrf
89 var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
90 c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
91 c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
92 c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
93 c.Response.Out.Header().Add("Referrer-Policy", "strict-origin-when-cross-origin")
94
95 fc[0](c, fc[1:]) // Execute the next filter stage.
96 }
97
98 //func ExampleStartupScript() {
99 // // revel.DevMod and revel.RunMode work here
100 // // Use this script to check for dev mode and set dev/prod startup scripts here!
101 // if revel.DevMode == true {
102 // // Dev mode
103 // }
104 //}
+0
-21
features/fixtures/revel/app/views/App/Index.html less more
0 {{set . "title" "Home"}}
1 {{template "header.html" .}}
2
3 <header class="jumbotron" style="background-color:#A9F16C">
4 <div class="container">
5 <div class="row">
6 <h1>It works!</h1>
7 <p></p>
8 </div>
9 </div>
10 </header>
11
12 <div class="container">
13 <div class="row">
14 <div class="span6">
15 {{template "flash.html" .}}
16 </div>
17 </div>
18 </div>
19
20 {{template "footer.html" .}}
+0
-64
features/fixtures/revel/app/views/debug.html less more
0 <style type="text/css">
1 #sidebar {
2 position: absolute;
3 right: 0px;
4 top:69px;
5 max-width: 75%;
6 z-index: 1000;
7 background-color: #fee;
8 border: thin solid grey;
9 padding: 10px;
10 }
11 #toggleSidebar {
12 position: absolute;
13 right: 0px;
14 top: 50px;
15 background-color: #fee;
16 }
17
18 </style>
19 <div id="sidebar" style="display:none;">
20 <h4>Available pipelines</h4>
21 <dl>
22 {{ range $index, $value := .}}
23 <dt>{{$index}}</dt>
24 <dd>{{$value}}</dd>
25 {{end}}
26 </dl>
27 <h4>Flash</h4>
28 <dl>
29 {{ range $index, $value := .flash}}
30 <dt>{{$index}}</dt>
31 <dd>{{$value}}</dd>
32 {{end}}
33 </dl>
34
35 <h4>Errors</h4>
36 <dl>
37 {{ range $index, $value := .errors}}
38 <dt>{{$index}}</dt>
39 <dd>{{$value}}</dd>
40 {{end}}
41 </dl>
42 </div>
43 <a id="toggleSidebar" href="#" class="toggles"><i class="glyphicon glyphicon-chevron-left"></i></a>
44
45 <script>
46 $sidebar = 0;
47 $('#toggleSidebar').click(function() {
48 if ($sidebar === 1) {
49 $('#sidebar').hide();
50 $('#toggleSidebar i').addClass('glyphicon-chevron-left');
51 $('#toggleSidebar i').removeClass('glyphicon-chevron-right');
52 $sidebar = 0;
53 }
54 else {
55 $('#sidebar').show();
56 $('#toggleSidebar i').addClass('glyphicon-chevron-right');
57 $('#toggleSidebar i').removeClass('glyphicon-chevron-left');
58 $sidebar = 1;
59 }
60
61 return false;
62 });
63 </script>
+0
-20
features/fixtures/revel/app/views/errors/404.html less more
0 <!DOCTYPE html>
1 <html lang="en">
2 <head>
3 <title>Not found</title>
4 </head>
5 <body>
6 {{if eq .RunMode "dev"}}
7 {{template "errors/404-dev.html" .}}
8 {{else}}
9 {{with .Error}}
10 <h1>
11 {{.Title}}
12 </h1>
13 <p>
14 {{.Description}}
15 </p>
16 {{end}}
17 {{end}}
18 </body>
19 </html>
+0
-16
features/fixtures/revel/app/views/errors/500.html less more
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>Application error</title>
4 </head>
5 <body>
6 {{if eq .RunMode "dev"}}
7 {{template "errors/500-dev.html" .}}
8 {{else}}
9 <h1>Oops, an error occured</h1>
10 <p>
11 This exception has been logged.
12 </p>
13 {{end}}
14 </body>
15 </html>
+0
-18
features/fixtures/revel/app/views/flash.html less more
0 {{if .flash.success}}
1 <div class="alert alert-success">
2 {{.flash.success}}
3 </div>
4 {{end}}
5
6 {{if or .errors .flash.error}}
7 <div class="alert alert-danger">
8 {{if .flash.error}}
9 {{.flash.error}}
10 {{end}}
11 <ul style="margin-top:10px;">
12 {{range .errors}}
13 <li>{{.}}</li>
14 {{end}}
15 </ul>
16 </div>
17 {{end}}
+0
-5
features/fixtures/revel/app/views/footer.html less more
0 {{if .DevMode}}
1 {{template "debug.html" .}}
2 {{end}}
3 </body>
4 </html>
+0
-19
features/fixtures/revel/app/views/header.html less more
0 <!DOCTYPE html>
1
2 <html>
3 <head>
4 <title>{{.title}}</title>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <link rel="stylesheet" type="text/css" href="/public/css/bootstrap-3.3.6.min.css">
8 <link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
9 <script src="/public/js/jquery-2.2.4.min.js"></script>
10 <script src="/public/js/bootstrap-3.3.6.min.js"></script>
11 {{range .moreStyles}}
12 <link rel="stylesheet" type="text/css" href="/public/{{.}}">
13 {{end}}
14 {{range .moreScripts}}
15 <script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
16 {{end}}
17 </head>
18 <body>
+0
-252
features/fixtures/revel/conf/app.conf less more
0 ################################################################################
1 # Revel configuration file
2 # More info at http://revel.github.io/manual/appconf.html
3 ################################################################################
4
5 # Revel build section
6 # This section contains values that are not reloadable
7 ################################################################################
8
9 # Comma delimited list of folders that are included with the package, or build commands
10 # If you want to not include folders within these ones prefix the folder with a . to make it hidden
11 package.folders = conf, public, app/views
12
13
14
15
16 # Revel reconfigurable section
17 #
18 ################################################################################
19
20
21 # Sets `revel.AppName` for use in-app.
22 # Example:
23 # `if revel.AppName {...}`
24 app.name = revel
25
26 # A secret string which is passed to cryptographically sign the cookie to prevent
27 # (and detect) user modification.
28 # Keep this string secret or users will be able to inject arbitrary cookie values
29 # into your application
30 app.secret = mJjWpo4H0rkeZHbLL9OOPNaQOK8YXKXwSqfetTMz1GwojTLMpFkFir0ER5haR4El
31
32 # Revel running behind proxy like nginx, haproxy, etc.
33 app.behind.proxy = false
34
35
36 # The IP address on which to listen.
37 http.addr =
38
39 # The port on which to listen.
40 http.port = 4515
41
42 # Whether to use SSL or not.
43 http.ssl = false
44
45 # Path to an X509 certificate file, if using SSL.
46 #http.sslcert =
47
48 # Path to an X509 certificate key, if using SSL.
49 #http.sslkey =
50
51
52 # Timeout specifies a time limit for request (in seconds) made by a single client.
53 # A Timeout of zero means no timeout.
54 http.timeout.read = 90
55 http.timeout.write = 60
56
57
58 # For any cookies set by Revel (Session,Flash,Error) these properties will set
59 # the fields of:
60 # http://golang.org/pkg/net/http/#Cookie
61 #
62 # Each cookie set by Revel is prefixed with this string.
63 cookie.prefix = REVEL
64
65 # A secure cookie has the secure attribute enabled and is only used via HTTPS,
66 # ensuring that the cookie is always encrypted when transmitting from client to
67 # server. This makes the cookie less likely to be exposed to cookie theft via
68 # eavesdropping.
69 #
70 # Defaults to false. If 'http.ssl' is enabled, this will be defaulted to true.
71 # This should only be true when Revel is handling SSL connections. If you are
72 # using a proxy in front of revel (Nginx, Apache, etc), then this should be left
73 # as false.
74 # cookie.secure = false
75
76 # Limit cookie access to a given domain.
77 #cookie.domain =
78
79 # Define when your session cookie expires.
80 # Values:
81 # "720h"
82 # A time duration (http://golang.org/pkg/time/#ParseDuration) after which
83 # the cookie expires and the session is invalid.
84 # "session"
85 # Sets a session cookie which invalidates the session when the user close
86 # the browser.
87 session.expires = 720h
88
89
90 # The date format used by Revel. Possible formats defined by the Go `time`
91 # package (http://golang.org/pkg/time/#Parse)
92 format.date = 2006-01-02
93 format.datetime = 2006-01-02 15:04
94
95
96 # Determines whether the template rendering should use chunked encoding.
97 # Chunked encoding can decrease the time to first byte on the client side by
98 # sending data before the entire template has been fully rendered.
99 results.chunked = false
100
101 # Compression of your HTML and CSS files with gzip typically saves around
102 # fifty to seventy percent of the file size. This means that it takes less
103 # time to load your pages, and less bandwidth is used over all.
104 # To enable compression, set value to true.
105 results.compressed = false
106
107
108
109 # The default language of this application.
110 i18n.default_language = en
111
112 # The default format when message is missing.
113 # The original message shows in %s
114 #i18n.unknown_format = "??? %s ???"
115
116
117 # Module to serve static content such as CSS, JavaScript and Media files
118 # Allows Routes like this:
119 # `Static.ServeModule("modulename","public")`
120 module.static = github.com/revel/modules/static
121
122 ################################################################################
123
124 # Section: dev
125 # This section is evaluated when running Revel in dev mode. Like so:
126 # `revel run path/to/myapp`
127 [dev]
128
129 # This sets `revel.DevMode` for use in-app.
130 # Example:
131 # `if revel.DevMode {...}`
132 # or in your templates with
133 # ``
134 # Values:
135 # "true"
136 # Sets `DevMode` to `true`.
137 # "false"
138 # Sets `DevMode` to `false`.
139 mode.dev = true
140
141
142 # Pretty print JSON/XML when calling RenderJSON/RenderXML
143 # Values:
144 # "true"
145 # Enables pretty printing.
146 # "false"
147 # Disables pretty printing.
148 results.pretty = true
149
150
151 # Watch your applicaton files for changes and automatically rebuild
152 # Values:
153 # "true"
154 # Enables auto rebuilding.
155 # "false"
156 # Disables auto rebuilding.
157 watch = true
158
159
160 # Define when to rebuild new changes.
161 # Values:
162 # "normal"
163 # Rebuild when a new request is received and changes have been detected.
164 # "eager"
165 # Rebuild as soon as changes are detected.
166 watch.mode = eager
167
168 # Watch the entire `$GOPATH` for changes.
169 # Values:
170 # "true"
171 # Includes `$GOPATH` in watch path.
172 # "false"
173 # Excludes `$GOPATH` from watch path. Default value.
174 #watch.gopath = true
175
176
177 # Module to run code tests in the browser
178 # See:
179 # http://revel.github.io/manual/testing.html
180 module.testrunner = github.com/revel/modules/testrunner
181
182
183 # Where to log the various Revel logs
184 # Values:
185 # "off"
186 # Disable log output.
187 # "stdout"
188 # Log to OS's standard output.
189 # "stderr"
190 # Log to Os's standard error output. Default value.
191 # "relative/path/to/log"
192 # Log to file.
193 log.all.filter.module.app = stdout # Log all loggers for the application to the stdout
194 log.error.nfilter.module.app = stderr # Everything else that logs an error to stderr
195 log.crit.output = stderr # Everything that logs something as critical goes to this
196
197 # Revel request access log
198 # Access log line format:
199 # INFO 21:53:55 static server-engine.go:169: Request Stats ip=127.0.0.1 path=/public/vendors/datatables.net-buttons/js/buttons.html5.min.js method=GET start=2017/08/31 21:53:55 status=200 duration_seconds=0.0002583 section=requestlog
200 log.request.output = stdout
201
202
203
204 ################################################################################
205 # Section: prod
206 # This section is evaluated when running Revel in production mode. Like so:
207 # `revel run path/to/myapp prod`
208 # See:
209 # [dev] section for documentation of the various settings
210 [prod]
211
212 mode.dev = false
213
214 results.pretty = false
215
216 watch = false
217
218 module.testrunner =
219
220 log.warn.output = log/%(app.name)s-warn.json # Log all warn messages to file
221 log.error.output = log/%(app.name)s-error.json # Log all errors to file
222 log.crit.output = log/%(app.name)s-critical.json # Log all critical to file
223
224 # Revel request access log (json format)
225 # Example:
226 # log.request.output = %(app.name)s-request.json
227 log.request.output = log/%(app.name)s-requests.json
228
229
230 # This profile will only be used when the USE_PROPERTIES_FILE_CONFIG environment variable is set
231
232 [configfile]
233 mode.dev = true
234 watch = true
235 watch.mode = eager
236 module.testrunner = github.com/revel/modules/testrunner
237
238 log.all.filter.module.app = stdout # Log all loggers for the application to the stdout
239 log.error.nfilter.module.app = stderr # Everything else that logs an error to stderr
240 log.crit.output = stderr # Everything that logs something as critical goes to this
241 log.request.output = stdout
242
243 bugsnag.apikey= a35a2a72bd230ac0aa0f52715bbdc6aa
244 bugsnag.apptype = config-file-app-type
245 bugsnag.appversion = 4.5.6
246 bugsnag.autocapturesessions= false
247 bugsnag.endpoints.notify = ${BUGSNAG_ENDPOINT}
248 bugsnag.endpoints.sessions = ${BUGSNAG_ENDPOINT}
249 bugsnag.hostname = config-file-server
250 bugsnag.releasestage= config-test
251
+0
-34
features/fixtures/revel/conf/routes less more
0 # Routes Config
1 #
2 # This file defines all application routes (Higher priority routes first)
3 #
4
5 module:testrunner
6 # module:jobs
7
8
9 GET / App.Index
10
11 # Ignore favicon requests
12 GET /favicon.ico 404
13
14 # Map static resources from the /app/public folder to the /public path
15 GET /public/*filepath Static.Serve("public")
16
17 GET /handled App.Handled
18 GET /autonotify-then-recover App.Unhandled
19 GET /session App.Session
20 GET /autonotify App.AutoNotify
21 GET /onbeforenotify App.OnBeforeNotify
22 GET /recover App.Recover
23 GET /user App.User
24
25 # Catch all, this will route any request into the controller path
26 #
27 # **** WARNING ****
28 # Enabling this exposes any controller and function to the web.
29 # ** This is a serious security issue if used online **
30 #
31 # For rapid development uncomment the following to add new controller.action endpoints
32 # without having to add them to the routes table.
33 # * /:controller/:action :controller.:action
+0
-8
features/fixtures/revel/messages/sample.en less more
0 # Sample messages file for the English language (en)
1 # Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)
2 # Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
3 # See also:
4 # - http://www.rfc-editor.org/rfc/bcp/bcp47.txt
5 # - http://www.w3.org/International/questions/qa-accept-lang-locales
6 [DEFAULT]
7
+0
-5
features/fixtures/revel/public/css/bootstrap-3.3.6.min.css less more
0 /*!
1 * Bootstrap v3.3.6 (http://getbootstrap.com)
2 * Copyright 2011-2015 Twitter, Inc.
3 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
4 *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */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,menu,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-color:transparent}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}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!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:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{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.woff2) format('woff2'),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:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.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"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-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}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;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}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;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}[role=button]{cursor:pointer}.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,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,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,.h5,.h6,h4,h5,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%}.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:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{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}dd,dt{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[data-original-title],abbr[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 ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer: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 .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}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%;font-weight:700;-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-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.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-md-1,.col-md-10,.col-md-11,.col-md-12,.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-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.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-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{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-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{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-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{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-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{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}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{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>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{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>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{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=checkbox],input[type=radio]{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=checkbox]:focus,input[type=radio]: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:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],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-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],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}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;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{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{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 .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{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 .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{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~.form-control-feedback{top:25px}.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 .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{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{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.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;-ms-touch-action:manipulation;touch-action:manipulation;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.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{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{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{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.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{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.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{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.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{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=button].btn-block,input[type=reset].btn-block,input[type=submit].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-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{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:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{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 dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@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-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.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,.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:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-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-left-radius:4px;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-right-radius:4px;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=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.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 .form-control:focus{z-index:3}.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.3333333;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}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.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}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn: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=checkbox],.input-group-addon input[type=radio]{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-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){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:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{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:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{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{z-index:2;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:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.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:focus,.nav-tabs>li.active>a:hover{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:focus,.nav-tabs.nav-justified>.active>a:hover{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:focus,.nav-tabs.nav-justified>.active>a:hover{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:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.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:focus,.nav-tabs-justified>.active>a:hover{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:focus,.nav-tabs-justified>.active>a:hover{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-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{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-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{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:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@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 .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{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-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 .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{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}.navbar-form .form-group:last-child{margin-bottom:0}}@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-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{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;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}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{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:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{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:focus,.navbar-default .navbar-nav>.open>a:hover{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:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{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:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{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:focus,.navbar-inverse .navbar-nav>.open>a:hover{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:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{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:#337ab7;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:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{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;line-height:1.3333333}.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;line-height:1.5}.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:focus,.pager li>a:hover{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:focus,.pager .disabled>a:hover,.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:focus,a.label:hover{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]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{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:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom: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,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .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:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.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:#337ab7;-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-bar-striped,.progress-striped .progress-bar{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-bar.active,.progress.active .progress-bar{-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-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{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.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}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{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>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>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,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.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,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table-responsive:first-child>.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 th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table-responsive:first-child>.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 th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table-responsive:last-child>.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 th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table-responsive:last-child>.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 th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.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,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.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>.list-group,.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:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.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 embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.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:focus,.close:hover{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:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(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{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-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.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;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{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;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;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-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;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;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);line-break:auto}.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;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>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.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);background-color:rgba(0,0,0,0);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:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.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-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row: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}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.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!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.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!important}tr.visible-md{display:table-row!important}td.visible-md,th.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!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.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!important}tr.visible-print{display:table-row!important}td.visible-print,th.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}}
features/fixtures/revel/public/fonts/glyphicons-halflings-regular.ttf less more
Binary diff not shown
features/fixtures/revel/public/fonts/glyphicons-halflings-regular.woff less more
Binary diff not shown
features/fixtures/revel/public/fonts/glyphicons-halflings-regular.woff2 less more
Binary diff not shown
features/fixtures/revel/public/img/favicon.png less more
Binary diff not shown
+0
-7
features/fixtures/revel/public/js/bootstrap-3.3.6.min.js less more
0 /*!
1 * Bootstrap v3.3.6 (http://getbootstrap.com)
2 * Copyright 2011-2015 Twitter, Inc.
3 * Licensed under the MIT license
4 */
5 if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");
6 d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.6",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
+0
-4
features/fixtures/revel/public/js/jquery-2.2.4.min.js less more
0 /*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */
1 !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=la(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=ma(b);function pa(){}pa.prototype=d.filters=d.pseudos,d.setFilters=new pa,g=fa.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=R.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=S.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(Q," ")}),h=h.slice(c.length));for(g in d.filter)!(e=W[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fa.error(a):z(a,i).slice(0)};function qa(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){n.each(b,function(b,c){n.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==n.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return n.each(arguments,function(a,b){var c;while((c=n.inArray(b,f,c))>-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c;
2 }catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=N.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function W(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&T.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var X=/^(?:checkbox|radio)$/i,Y=/<([\w:-]+)/,Z=/^$|\/(?:java|ecma)script/i,$={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,e,f,g=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||d,e=c.documentElement,f=c.body,a.pageX=b.clientX+(e&&e.scrollLeft||f&&f.scrollLeft||0)-(e&&e.clientLeft||f&&f.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||f&&f.scrollTop||0)-(e&&e.clientTop||f&&f.clientTop||0)),a.which||void 0===g||(a.which=1&g?1:2&g?3:4&g?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,e,f=a.type,g=a,h=this.fixHooks[f];h||(this.fixHooks[f]=h=ea.test(f)?this.mouseHooks:da.test(f)?this.keyHooks:{}),e=h.props?this.props.concat(h.props):this.props,a=new n.Event(g),b=e.length;while(b--)c=e[b],a[c]=g[c];return a.target||(a.target=d),3===a.target.nodeType&&(a.target=a.target.parentNode),h.filter?h.filter(a,g):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==ia()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===ia()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ga:ha):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={constructor:n.Event,isDefaultPrevented:ha,isPropagationStopped:ha,isImmediatePropagationStopped:ha,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ga,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ga,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ga,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||n.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),n.fn.extend({on:function(a,b,c,d){return ja(this,a,b,c,d)},one:function(a,b,c,d){return ja(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=ha),this.each(function(){n.event.remove(this,a,c,b)})}});var ka=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,la=/<script|<style|<link/i,ma=/checked\s*(?:[^=]|=\s*.checked.)/i,na=/^true\/(.*)/,oa=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=wa[0].contentDocument,b.write(),b.close(),c=ya(a,b),wa.detach()),xa[a]=c),c}var Aa=/^margin/,Ba=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ca=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)},Da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e},Ea=d.documentElement;!function(){var b,c,e,f,g=d.createElement("div"),h=d.createElement("div");if(h.style){h.style.backgroundClip="content-box",h.cloneNode(!0).style.backgroundClip="",l.clearCloneStyle="content-box"===h.style.backgroundClip,g.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",g.appendChild(h);function i(){h.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",h.innerHTML="",Ea.appendChild(g);var d=a.getComputedStyle(h);b="1%"!==d.top,f="2px"===d.marginLeft,c="4px"===d.width,h.style.marginRight="50%",e="4px"===d.marginRight,Ea.removeChild(g)}n.extend(l,{pixelPosition:function(){return i(),b},boxSizingReliable:function(){return null==c&&i(),c},pixelMarginRight:function(){return null==c&&i(),e},reliableMarginLeft:function(){return null==c&&i(),f},reliableMarginRight:function(){var b,c=h.appendChild(d.createElement("div"));return c.style.cssText=h.style.cssText="-webkit-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",h.style.width="1px",Ea.appendChild(g),b=!parseFloat(a.getComputedStyle(c).marginRight),Ea.removeChild(g),h.removeChild(c),b}})}}();function Fa(a,b,c){var d,e,f,g,h=a.style;return c=c||Ca(a),g=c?c.getPropertyValue(b)||c[b]:void 0,""!==g&&void 0!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),c&&!l.pixelMarginRight()&&Ba.test(g)&&Aa.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f),void 0!==g?g+"":g}function Ga(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Ha=/^(none|table(?!-c[ea]).+)/,Ia={position:"absolute",visibility:"hidden",display:"block"},Ja={letterSpacing:"0",fontWeight:"400"},Ka=["Webkit","O","Moz","ms"],La=d.createElement("div").style;function Ma(a){if(a in La)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ka.length;while(c--)if(a=Ka[c]+b,a in La)return a}function Na(a,b,c){var d=T.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Oa(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+U[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+U[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+U[f]+"Width",!0,e))):(g+=n.css(a,"padding"+U[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+U[f]+"Width",!0,e)));return g}function Pa(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ca(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Fa(a,b,f),(0>e||null==e)&&(e=a.style[b]),Ba.test(e))return e;d=g&&(l.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Oa(a,b,c||(g?"border":"content"),d,f)+"px"}function Qa(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=N.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&V(d)&&(f[g]=N.access(d,"olddisplay",za(d.nodeName)))):(e=V(d),"none"===c&&e||N.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Fa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=T.exec(c))&&e[1]&&(c=W(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(n.cssNumber[h]?"":"px")),l.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Ma(h)||h),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Fa(a,b,d)),"normal"===e&&b in Ja&&(e=Ja[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?Ha.test(n.css(a,"display"))&&0===a.offsetWidth?Da(a,Ia,function(){return Pa(a,b,d)}):Pa(a,b,d):void 0},set:function(a,c,d){var e,f=d&&Ca(a),g=d&&Oa(a,b,d,"border-box"===n.css(a,"boxSizing",!1,f),f);return g&&(e=T.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=n.css(a,b)),Na(a,c,g)}}}),n.cssHooks.marginLeft=Ga(l.reliableMarginLeft,function(a,b){return b?(parseFloat(Fa(a,"marginLeft"))||a.getBoundingClientRect().left-Da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px":void 0}),n.cssHooks.marginRight=Ga(l.reliableMarginRight,function(a,b){return b?Da(a,{display:"inline-block"},Fa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+U[d]+b]=f[d]||f[d-2]||f[0];return e}},Aa.test(a)||(n.cssHooks[a+b].set=Na)}),n.fn.extend({css:function(a,b){return K(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ca(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Qa(this,!0)},hide:function(){return Qa(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){V(this)?n(this).show():n(this).hide()})}});function Ra(a,b,c,d,e){return new Ra.prototype.init(a,b,c,d,e)}n.Tween=Ra,Ra.prototype={constructor:Ra,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ra.propHooks[this.prop];return a&&a.get?a.get(this):Ra.propHooks._default.get(this)},run:function(a){var b,c=Ra.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ra.propHooks._default.set(this),this}},Ra.prototype.init.prototype=Ra.prototype,Ra.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},Ra.propHooks.scrollTop=Ra.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=Ra.prototype.init,n.fx.step={};var Sa,Ta,Ua=/^(?:toggle|show|hide)$/,Va=/queueHooks$/;function Wa(){return a.setTimeout(function(){Sa=void 0}),Sa=n.now()}function Xa(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=U[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ya(a,b,c){for(var d,e=(_a.tweeners[b]||[]).concat(_a.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Za(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&V(a),q=N.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?N.get(a,"olddisplay")||za(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Ua.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?za(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=N.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;N.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ya(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function $a(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function _a(a,b,c){var d,e,f=0,g=_a.prefilters.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Sa||Wa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{},easing:n.easing._default},c),originalProperties:b,originalOptions:c,startTime:Sa||Wa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for($a(k,j.opts.specialEasing);g>f;f++)if(d=_a.prefilters[f].call(j,a,k,j.opts))return n.isFunction(d.stop)&&(n._queueHooks(j.elem,j.opts.queue).stop=n.proxy(d.stop,d)),d;return n.map(k,Ya,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(_a,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return W(c.elem,a,T.exec(b),c),c}]},tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.match(G);for(var c,d=0,e=a.length;e>d;d++)c=a[d],_a.tweeners[c]=_a.tweeners[c]||[],_a.tweeners[c].unshift(b)},prefilters:[Za],prefilter:function(a,b){b?_a.prefilters.unshift(a):_a.prefilters.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,null!=d.queue&&d.queue!==!0||(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(V).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=_a(this,n.extend({},a),f);(e||N.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=N.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Va.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=N.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Xa(b,!0),a,d,e)}}),n.each({slideDown:Xa("show"),slideUp:Xa("hide"),slideToggle:Xa("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Sa=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Sa=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ta||(Ta=a.setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){a.clearInterval(Ta),Ta=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(b,c){return b=n.fx?n.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",l.checkOn=""!==a.value,l.optSelected=c.selected,b.disabled=!0,l.optDisabled=!c.disabled,a=d.createElement("input"),a.value="t",a.type="radio",l.radioValue="t"===a.value}();var ab,bb=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return K(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),e=n.attrHooks[b]||(n.expr.match.bool.test(b)?ab:void 0)),void 0!==c?null===c?void n.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=n.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!l.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(G);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)}}),ab={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=bb[b]||n.find.attr;bb[b]=function(a,b,d){var e,f;return d||(f=bb[b],bb[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,bb[b]=f),e}});var cb=/^(?:input|select|textarea|button)$/i,db=/^(?:a|area)$/i;n.fn.extend({prop:function(a,b){return K(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&n.isXMLDoc(a)||(b=n.propFix[b]||b,e=n.propHooks[b]),
3 void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):cb.test(a.nodeName)||db.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var eb=/[\t\r\n\f]/g;function fb(a){return a.getAttribute&&a.getAttribute("class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,fb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,fb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,fb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=fb(this),b&&N.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":N.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+fb(c)+" ").replace(eb," ").indexOf(b)>-1)return!0;return!1}});var gb=/\r/g,hb=/[\x20\t\r\n\f]+/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(gb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a)).replace(hb," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(n.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var ib=/^(?:focusinfocus|focusoutblur)$/;n.extend(n.event,{trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!ib.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),l=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},f||!o.trigger||o.trigger.apply(e,c)!==!1)){if(!f&&!o.noBubble&&!n.isWindow(e)){for(j=o.delegateType||q,ib.test(j+q)||(h=h.parentNode);h;h=h.parentNode)p.push(h),i=h;i===(e.ownerDocument||d)&&p.push(i.defaultView||i.parentWindow||a)}g=0;while((h=p[g++])&&!b.isPropagationStopped())b.type=g>1?j:o.bindType||q,m=(N.get(h,"events")||{})[b.type]&&N.get(h,"handle"),m&&m.apply(h,c),m=l&&h[l],m&&m.apply&&L(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=q,f||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!L(e)||l&&n.isFunction(e[q])&&!n.isWindow(e)&&(i=e[l],i&&(e[l]=null),n.event.triggered=q,e[q](),n.event.triggered=void 0,i&&(e[l]=i)),b.result}},simulate:function(a,b,c){var d=n.extend(new n.Event,c,{type:a,isSimulated:!0});n.event.trigger(d,null,b)}}),n.fn.extend({trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),l.focusin="onfocusin"in a,l.focusin||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a))};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=N.access(d,b);e||d.addEventListener(a,c,!0),N.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=N.access(d,b)-1;e?N.access(d,b,e):(d.removeEventListener(a,c,!0),N.remove(d,b))}}});var jb=a.location,kb=n.now(),lb=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||n.error("Invalid XML: "+b),c};var mb=/#.*$/,nb=/([?&])_=[^&]*/,ob=/^(.*?):[ \t]*([^\r\n]*)$/gm,pb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,qb=/^(?:GET|HEAD)$/,rb=/^\/\//,sb={},tb={},ub="*/".concat("*"),vb=d.createElement("a");vb.href=jb.href;function wb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(G)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function xb(a,b,c,d){var e={},f=a===tb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function yb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function zb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Ab(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:jb.href,type:"GET",isLocal:pb.test(jb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":ub,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?yb(yb(a,n.ajaxSettings),b):yb(n.ajaxSettings,a)},ajaxPrefilter:wb(sb),ajaxTransport:wb(tb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m=n.ajaxSetup({},c),o=m.context||m,p=m.context&&(o.nodeType||o.jquery)?n(o):n.event,q=n.Deferred(),r=n.Callbacks("once memory"),s=m.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,getResponseHeader:function(a){var b;if(2===v){if(!h){h={};while(b=ob.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===v?g:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return v||(a=u[c]=u[c]||a,t[a]=b),this},overrideMimeType:function(a){return v||(m.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>v)for(b in a)s[b]=[s[b],a[b]];else x.always(a[x.status]);return this},abort:function(a){var b=a||w;return e&&e.abort(b),z(0,b),this}};if(q.promise(x).complete=r.add,x.success=x.done,x.error=x.fail,m.url=((b||m.url||jb.href)+"").replace(mb,"").replace(rb,jb.protocol+"//"),m.type=c.method||c.type||m.method||m.type,m.dataTypes=n.trim(m.dataType||"*").toLowerCase().match(G)||[""],null==m.crossDomain){j=d.createElement("a");try{j.href=m.url,j.href=j.href,m.crossDomain=vb.protocol+"//"+vb.host!=j.protocol+"//"+j.host}catch(y){m.crossDomain=!0}}if(m.data&&m.processData&&"string"!=typeof m.data&&(m.data=n.param(m.data,m.traditional)),xb(sb,m,c,x),2===v)return x;k=n.event&&m.global,k&&0===n.active++&&n.event.trigger("ajaxStart"),m.type=m.type.toUpperCase(),m.hasContent=!qb.test(m.type),f=m.url,m.hasContent||(m.data&&(f=m.url+=(lb.test(f)?"&":"?")+m.data,delete m.data),m.cache===!1&&(m.url=nb.test(f)?f.replace(nb,"$1_="+kb++):f+(lb.test(f)?"&":"?")+"_="+kb++)),m.ifModified&&(n.lastModified[f]&&x.setRequestHeader("If-Modified-Since",n.lastModified[f]),n.etag[f]&&x.setRequestHeader("If-None-Match",n.etag[f])),(m.data&&m.hasContent&&m.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",m.contentType),x.setRequestHeader("Accept",m.dataTypes[0]&&m.accepts[m.dataTypes[0]]?m.accepts[m.dataTypes[0]]+("*"!==m.dataTypes[0]?", "+ub+"; q=0.01":""):m.accepts["*"]);for(l in m.headers)x.setRequestHeader(l,m.headers[l]);if(m.beforeSend&&(m.beforeSend.call(o,x,m)===!1||2===v))return x.abort();w="abort";for(l in{success:1,error:1,complete:1})x[l](m[l]);if(e=xb(tb,m,c,x)){if(x.readyState=1,k&&p.trigger("ajaxSend",[x,m]),2===v)return x;m.async&&m.timeout>0&&(i=a.setTimeout(function(){x.abort("timeout")},m.timeout));try{v=1,e.send(t,z)}catch(y){if(!(2>v))throw y;z(-1,y)}}else z(-1,"No Transport");function z(b,c,d,h){var j,l,t,u,w,y=c;2!==v&&(v=2,i&&a.clearTimeout(i),e=void 0,g=h||"",x.readyState=b>0?4:0,j=b>=200&&300>b||304===b,d&&(u=zb(m,x,d)),u=Ab(m,u,x,j),j?(m.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(n.lastModified[f]=w),w=x.getResponseHeader("etag"),w&&(n.etag[f]=w)),204===b||"HEAD"===m.type?y="nocontent":304===b?y="notmodified":(y=u.state,l=u.data,t=u.error,j=!t)):(t=y,!b&&y||(y="error",0>b&&(b=0))),x.status=b,x.statusText=(c||y)+"",j?q.resolveWith(o,[l,y,x]):q.rejectWith(o,[x,y,t]),x.statusCode(s),s=void 0,k&&p.trigger(j?"ajaxSuccess":"ajaxError",[x,m,j?l:t]),r.fireWith(o,[x,y]),k&&(p.trigger("ajaxComplete",[x,m]),--n.active||n.event.trigger("ajaxStop")))}return x},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax(n.extend({url:a,type:b,dataType:e,data:c,success:d},n.isPlainObject(a)&&a))}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return n.isFunction(a)?this.each(function(b){n(this).wrapInner(a.call(this,b))}):this.each(function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return!n.expr.filters.visible(a)},n.expr.filters.visible=function(a){return a.offsetWidth>0||a.offsetHeight>0||a.getClientRects().length>0};var Bb=/%20/g,Cb=/\[\]$/,Db=/\r?\n/g,Eb=/^(?:submit|button|image|reset|file)$/i,Fb=/^(?:input|select|textarea|keygen)/i;function Gb(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||Cb.test(a)?d(a,e):Gb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Gb(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Gb(c,a[c],b,e);return d.join("&").replace(Bb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Fb.test(this.nodeName)&&!Eb.test(a)&&(this.checked||!X.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(Db,"\r\n")}}):{name:b.name,value:c.replace(Db,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Hb={0:200,1223:204},Ib=n.ajaxSettings.xhr();l.cors=!!Ib&&"withCredentials"in Ib,l.ajax=Ib=!!Ib,n.ajaxTransport(function(b){var c,d;return l.cors||Ib&&!b.crossDomain?{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Hb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=n("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Jb=[],Kb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Jb.pop()||n.expando+"_"+kb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Kb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Kb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Kb,"$1"+e):b.jsonp!==!1&&(b.url+=(lb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?n(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Jb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||d;var e=x.exec(a),f=!c&&[];return e?[b.createElement(e[1])]:(e=ca([a],b,f),f&&f.length&&n(f).remove(),n.merge([],e.childNodes))};var Lb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Lb)return Lb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};function Mb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,n.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(e=d.getBoundingClientRect(),c=Mb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ea})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;n.fn[a]=function(d){return K(this,function(a,d,e){var f=Mb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Ga(l.pixelPosition,function(a,c){return c?(c=Fa(a,b),Ba.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return K(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)},size:function(){return this.length}}),n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Nb=a.jQuery,Ob=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Ob),b&&a.jQuery===n&&(a.jQuery=Nb),n},b||(a.jQuery=a.$=n),n});
+0
-11
features/fixtures/revel/run.sh less more
0 #!/bin/sh
1
2 set -e
3
4 # Use the [configfile] profile declared in conf/app.conf if the USE_PROPERTIES_FILE_CONFIG is set
5 if [ -z "${USE_PROPERTIES_FILE_CONFIG}" ]
6 then
7 $GOPATH/bin/revel run test
8 else
9 $GOPATH/bin/revel run test configfile
10 fi
+0
-23
features/fixtures/revel/tests/apptest.go less more
0 package tests
1
2 import (
3 "github.com/revel/revel/testing"
4 )
5
6 type AppTest struct {
7 testing.TestSuite
8 }
9
10 func (t *AppTest) Before() {
11 println("Set up")
12 }
13
14 func (t *AppTest) TestThatIndexPageWorks() {
15 t.Get("/")
16 t.AssertOk()
17 t.AssertContentType("text/html; charset=utf-8")
18 }
19
20 func (t *AppTest) After() {
21 println("Tear down")
22 }
+0
-27
features/gin_features/appversion.feature less more
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I set environment variable "SERVER_PORT" to "4511"
7
8 Scenario: A error report contains the configured app type
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "gin"
11 And I wait for the app to open port "4511"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4511/handled"
14 Then I wait to receive a request
15 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.version" equals "3.1.2"
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "gin"
21 And I wait for the app to open port "4511"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4511/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.version" equals "3.1.2"
+0
-24
features/gin_features/autocapturesessions.feature less more
0 Feature: Configure auto capture sessions
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6
7 Scenario: A session is not sent if auto capture sessions is off
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "gin"
10 And I wait for the app to open port "4511"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4511/session"
13 And I wait for 2 seconds
14 Then I should receive no requests
15
16 Scenario: A session is sent if auto capture sessions is on
17 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I start the service "gin"
19 And I wait for the app to open port "4511"
20 And I wait for 2 seconds
21 And I open the URL "http://localhost:4511/session"
22 Then I wait to receive a request after the start up session
23 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
+0
-33
features/gin_features/autonotify.feature less more
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6
7 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "gin"
10 And I wait for the app to open port "4511"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4511/autonotify-then-recover"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is true
16 And the event "severityReason.type" equals "unhandledErrorMiddleware" for request 0
17 And the event "severityReason.attributes.framework" equals "Gin" for request 0
18 And the exception "errorClass" equals "*runtime.TypeAssertionError"
19 And the exception "message" matches "interface conversion: interface ({} )?is struct {}, not string"
20
21 Scenario: An error report is sent when a go routine crashes which is reported through auto notify
22 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
23 When I start the service "gin"
24 And I wait for the app to open port "4511"
25 And I wait for 2 seconds
26 And I open the URL "http://localhost:4511/autonotify"
27 Then I wait to receive a request
28 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is true
30 And the event "severityReason.type" equals "handledPanic" for request 0
31 And the exception "errorClass" equals "*errors.errorString"
32 And the exception "message" equals "Go routine killed with auto notify"
+0
-34
features/gin_features/handled.feature less more
0 Feature: Handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I start the service "gin"
10 And I wait for the app to open port "4511"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4511/handled"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is false for request 0
16 And the event "severity" equals "warning" for request 0
17 And the event "severityReason.type" equals "handledError" for request 0
18 And the exception "errorClass" equals "*os.PathError" for request 0
19 And the "file" of stack frame 0 equals "main.go" for request 0
20
21 Scenario: A handled error sends a report with a custom name
22 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
23 When I start the service "gin"
24 And I wait for the app to open port "4511"
25 And I wait for 2 seconds
26 And I open the URL "http://localhost:4511/handled"
27 Then I wait to receive a request
28 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is false for request 0
30 And the event "severity" equals "warning" for request 0
31 And the event "severityReason.type" equals "handledError" for request 0
32 And the exception "errorClass" equals "MyCustomErrorClass" for request 0
33 And the "file" of stack frame 0 equals "main.go" for request 0
+0
-18
features/gin_features/onbeforenotify.feature less more
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
9 When I start the service "gin"
10 And I wait for the app to open port "4511"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4511/onbeforenotify"
13 Then I wait to receive 2 requests
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "message" equals "Don't ignore this error" for request 0
16 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the exception "message" equals "Error message was changed" for request 1
+0
-17
features/gin_features/recover.feature less more
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report is sent when request crashes but is recovered
9 When I start the service "gin"
10 And I wait for the app to open port "4511"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4511/recover"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "errorClass" equals "*errors.errorString"
16 And the exception "message" equals "Request killed but recovered"
+0
-27
features/gin_features/releasestage.feature less more
0 Feature: Configuring release stage
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6 And I set environment variable "RELEASE_STAGE" to "my-stage"
7
8 Scenario: An error report is sent with configured release stage
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "gin"
11 And I wait for the app to open port "4511"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4511/handled"
14 Then I wait to receive a request
15 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.releaseStage" equals "my-stage" for request 0
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "gin"
21 And I wait for the app to open port "4511"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4511/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.releaseStage" equals "my-stage"
+0
-20
features/gin_features/request.feature less more
0 Feature: Capturing request information automatically
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6
7 Scenario: An error report will automatically contain request information
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "gin"
10 And I wait for the app to open port "4511"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4511/handled"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "request.clientIp" is not null
16 And the event "request.headers.User-Agent" equals "Ruby"
17 And the event "request.httpMethod" equals "GET"
18 And the event "request.url" ends with "/handled"
19 And the event "request.url" starts with "http://"
+0
-21
features/gin_features/user.feature less more
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4511"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I start the service "gin"
13 And I wait for the app to open port "4511"
14 And I wait for 2 seconds
15 And I open the URL "http://localhost:4511/user"
16 Then I wait to receive a request
17 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
18 And the event "user.id" equals "test-user-id"
19 And the event "user.name" equals "test-user-name"
20 And the event "user.email" equals "test-user-email"
0 Feature: Plain handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I run the go service "app" with the test case "handled"
10 Then I wait to receive a request
11 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the event "unhandled" is false
13 And the event "severity" equals "warning"
14 And the event "severityReason.type" equals "handledError"
15 And the exception is a PathError for request 0
16 And the "file" of stack frame 0 equals "main.go"
17
18 Scenario: A handled error sends a report with a custom name
19 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
20 When I run the go service "app" with the test case "handled"
21 Then I wait to receive a request
22 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
23 And the event "unhandled" is false
24 And the event "severity" equals "warning"
25 And the event "severityReason.type" equals "handledError"
26 And the exception "errorClass" equals "MyCustomErrorClass"
27 And the "file" of stack frame 0 equals "main.go"
28
29 Scenario: Sending an event using a callback to modify report contents
30 When I run the go service "app" with the test case "handled-with-callback"
31 Then I wait to receive a request
32 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
33 And the event "unhandled" is false
34 And the event "severity" equals "info"
35 And the event "severityReason.type" equals "userCallbackSetSeverity"
36 And the event "context" equals "nonfatal.go:14"
37 And the "file" of stack frame 0 equals "main.go"
38 And stack frame 0 contains a local function spanning 241 to 247
39 And the "file" of stack frame 1 equals ">insertion<"
40 And the "lineNumber" of stack frame 1 equals 0
41
42 Scenario: Marking an error as unhandled in a callback
43 When I run the go service "app" with the test case "make-unhandled-with-callback"
44 Then I wait to receive a request
45 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
46 And the event "unhandled" is true
47 And the event "severity" equals "error"
48 And the event "severityReason.type" equals "userCallbackSetSeverity"
49 And the event "severityReason.unhandledOverridden" is true
50 And the "file" of stack frame 0 equals "main.go"
51 And stack frame 0 contains a local function spanning 253 to 256
52
53 Scenario: Unwrapping the causes of a handled error
54 When I run the go service "app" with the test case "nested-error"
55 Then I wait to receive a request
56 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
57 And the event "unhandled" is false
58 And the event "severity" equals "warning"
59 And the event "exceptions.0.message" equals "terminate process"
60 And the "lineNumber" of stack frame 0 equals 291
61 And the "file" of stack frame 0 equals "main.go"
62 And the "method" of stack frame 0 equals "nestedHandledError"
63 And the event "exceptions.1.message" equals "login failed"
64 And the event "exceptions.1.stacktrace.0.file" equals "main.go"
65 And the event "exceptions.1.stacktrace.0.lineNumber" equals 311
66 And the event "exceptions.2.message" equals "invalid token"
67 And the event "exceptions.2.stacktrace.0.file" equals "main.go"
68 And the event "exceptions.2.stacktrace.0.lineNumber" equals 319
0 Feature: Configuring hostname
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report contains the configured hostname
8 Given I set environment variable "HOSTNAME" to "server-1a"
9 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I run the go service "app" with the test case "handled"
11 And I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "device.hostname" equals "server-1a"
14
15 Scenario: An session report contains the configured hostname
16 Given I set environment variable "HOSTNAME" to "server-1a"
17 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I run the go service "app" with the test case "session"
19 And I wait to receive a request after the start up session
20 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
21 And the payload field "device.hostname" equals "server-1a"
+0
-28
features/martini_features/appversion.feature less more
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I set environment variable "SERVER_PORT" to "4513"
7
8 Scenario: A error report contains the configured app type
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "martini"
11 And I wait for the app to open port "4513"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4513/handled"
14 Then I wait to receive a request
15 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.version" equals "3.1.2"
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "martini"
21 And I wait for the app to open port "4513"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4513/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.version" equals "3.1.2"
27
+0
-24
features/martini_features/autocapturesessions.feature less more
0 Feature: Configure auto capture sessions
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4513"
6
7 Scenario: A session is not sent if auto capture sessions is off
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "martini"
10 And I wait for the app to open port "4513"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4513/session"
13 And I wait for 2 seconds
14 Then I should receive no requests
15
16 Scenario: A session is sent if auto capture sessions is on
17 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I start the service "martini"
19 And I wait for the app to open port "4513"
20 And I wait for 2 seconds
21 And I open the URL "http://localhost:4513/session"
22 Then I wait to receive a request after the start up session
23 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
+0
-32
features/martini_features/autonotify.feature less more
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I set environment variable "SERVER_PORT" to "4513"
7
8 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
9 When I start the service "martini"
10 And I wait for the app to open port "4513"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4513/autonotify-then-recover"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is true
16 And the event "severityReason.type" equals "unhandledErrorMiddleware" for request 0
17 And the event "severityReason.attributes.framework" equals "Martini" for request 0
18 And the exception "errorClass" equals "*runtime.TypeAssertionError"
19 And the exception "message" matches "interface conversion: interface ({} )?is struct {}, not string"
20
21 Scenario: An error report is sent when a go routine crashes which is reported through auto notify
22 When I start the service "martini"
23 And I wait for the app to open port "4513"
24 And I wait for 2 seconds
25 And I open the URL "http://localhost:4513/autonotify"
26 Then I wait to receive a request
27 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
28 And the event "unhandled" is true
29 And the event "severityReason.type" equals "handledPanic" for request 0
30 And the exception "errorClass" equals "*errors.errorString"
31 And the exception "message" equals "Go routine killed with auto notify"
+0
-34
features/martini_features/handled.feature less more
0 Feature: Handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4513"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I start the service "martini"
10 And I wait for the app to open port "4513"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4513/handled"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is false for request 0
16 And the event "severity" equals "warning" for request 0
17 And the event "severityReason.type" equals "handledError" for request 0
18 And the exception "errorClass" equals "*os.PathError" for request 0
19 And the "file" of stack frame 0 equals "main.go" for request 0
20
21 Scenario: A handled error sends a report with a custom name
22 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
23 When I start the service "martini"
24 And I wait for the app to open port "4513"
25 And I wait for 2 seconds
26 And I open the URL "http://localhost:4513/handled"
27 Then I wait to receive a request
28 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is false for request 0
30 And the event "severity" equals "warning" for request 0
31 And the event "severityReason.type" equals "handledError" for request 0
32 And the exception "errorClass" equals "MyCustomErrorClass" for request 0
33 And the "file" of stack frame 0 equals "main.go" for request 0
+0
-18
features/martini_features/onbeforenotify.feature less more
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4513"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
9 When I start the service "martini"
10 And I wait for the app to open port "4513"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4513/onbeforenotify"
13 Then I wait to receive 2 requests
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "message" equals "Don't ignore this error" for request 0
16 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the exception "message" equals "Error message was changed" for request 1
+0
-17
features/martini_features/recover.feature less more
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4513"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report and session is sent when request crashes but is recovered
9 When I start the service "martini"
10 And I wait for the app to open port "4513"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4513/recover"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "errorClass" equals "*errors.errorString"
16 And the exception "message" equals "Request killed but recovered"
+0
-27
features/martini_features/releasestage.feature less more
0 Feature: Configuring release stage
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4513"
6 And I set environment variable "RELEASE_STAGE" to "my-stage"
7
8 Scenario: An error report is sent with configured release stage
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "martini"
11 And I wait for the app to open port "4513"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4513/handled"
14 Then I wait to receive a request
15 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.releaseStage" equals "my-stage" for request 0
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "martini"
21 And I wait for the app to open port "4513"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4513/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.releaseStage" equals "my-stage"
+0
-20
features/martini_features/request.feature less more
0 Feature: Capturing request information automatically
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4513"
6
7 Scenario: An error report will automatically contain request information
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "martini"
10 And I wait for the app to open port "4513"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4513/handled"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "request.clientIp" is not null
16 And the event "request.headers.User-Agent" equals "Ruby"
17 And the event "request.httpMethod" equals "GET"
18 And the event "request.url" ends with "/handled"
19 And the event "request.url" starts with "http://"
+0
-21
features/martini_features/user.feature less more
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4513"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I start the service "martini"
13 And I wait for the app to open port "4513"
14 And I wait for 2 seconds
15 And I open the URL "http://localhost:4513/user"
16 Then I wait to receive a request
17 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
18 And the event "user.id" equals "test-user-id"
19 And the event "user.name" equals "test-user-name"
20 And the event "user.email" equals "test-user-email"
0 Feature: Sending meta data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report contains custom meta data
9 When I run the go service "app" with the test case "metadata"
10 And I wait to receive a request
11 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the event "metaData.Scheme.Customer.Name" equals "Joe Bloggs"
13 And the event "metaData.Scheme.Customer.Age" equals "21"
14 And the event "metaData.Scheme.Level" equals "Blue"
0 Feature: Reporting multiple handled and unhandled errors in the same session
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Handled errors know about previous reported handled errors
9 When I run the go service "app" with the test case "multiple-handled"
10 And I wait to receive 2 requests
11 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event handled sessions count equals 1 for request 0
14 And the event handled sessions count equals 2 for request 1
15
16 Scenario: Unhandled errors know about previous reported handled errors
17 When I run the go service "app" with the test case "multiple-unhandled"
18 And I wait to receive 2 requests
19 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
21 And the event unhandled sessions count equals 1 for request 0
22 And the event unhandled sessions count equals 2 for request 1
+0
-27
features/negroni_features/appversion.feature less more
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I set environment variable "SERVER_PORT" to "4514"
7
8 Scenario: A error report contains the configured app type
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "negroni"
11 And I wait for the app to open port "4514"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4514/handled"
14 Then I wait to receive a request
15 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.version" equals "3.1.2"
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "negroni"
21 And I wait for the app to open port "4514"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4514/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.version" equals "3.1.2"
+0
-24
features/negroni_features/autocapturesessions.feature less more
0 Feature: Configure auto capture sessions
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6
7 Scenario: A session is not sent if auto capture sessions is off
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "negroni"
10 And I wait for the app to open port "4514"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4514/session"
13 And I wait for 2 seconds
14 Then I should receive no requests
15
16 Scenario: A session is sent if auto capture sessions is on
17 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I start the service "negroni"
19 And I wait for the app to open port "4514"
20 And I wait for 2 seconds
21 And I open the URL "http://localhost:4514/session"
22 Then I wait to receive a request after the start up session
23 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
+0
-33
features/negroni_features/autonotify.feature less more
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6
7 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "negroni"
10 And I wait for the app to open port "4514"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4514/autonotify-then-recover"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is true
16 And the event "severityReason.type" equals "unhandledErrorMiddleware" for request 0
17 And the event "severityReason.attributes.framework" equals "Negroni" for request 0
18 And the exception "errorClass" equals "*runtime.TypeAssertionError"
19 And the exception "message" matches "interface conversion: interface ({} )?is struct {}, not string"
20
21 Scenario: An error report is sent when a go routine crashes which is reported through auto notify
22 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
23 When I start the service "negroni"
24 And I wait for the app to open port "4514"
25 And I wait for 2 seconds
26 And I open the URL "http://localhost:4514/autonotify"
27 Then I wait to receive a request
28 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is true
30 And the event "severityReason.type" equals "handledPanic" for request 0
31 And the exception "errorClass" equals "*errors.errorString"
32 And the exception "message" equals "Go routine killed with auto notify"
+0
-34
features/negroni_features/handled.feature less more
0 Feature: Handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I start the service "negroni"
10 And I wait for the app to open port "4514"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4514/handled"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is false for request 0
16 And the event "severity" equals "warning" for request 0
17 And the event "severityReason.type" equals "handledError" for request 0
18 And the exception "errorClass" equals "*os.PathError" for request 0
19 And the "file" of stack frame 0 equals "main.go" for request 0
20
21 Scenario: A handled error sends a report with a custom name
22 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
23 When I start the service "negroni"
24 And I wait for the app to open port "4514"
25 And I wait for 2 seconds
26 And I open the URL "http://localhost:4514/handled"
27 Then I wait to receive a request
28 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is false for request 0
30 And the event "severity" equals "warning" for request 0
31 And the event "severityReason.type" equals "handledError" for request 0
32 And the exception "errorClass" equals "MyCustomErrorClass" for request 0
33 And the "file" of stack frame 0 equals "main.go" for request 0
+0
-18
features/negroni_features/onbeforenotify.feature less more
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
9 When I start the service "negroni"
10 And I wait for the app to open port "4514"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4514/onbeforenotify"
13 Then I wait to receive 2 requests
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "message" equals "Don't ignore this error" for request 0
16 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the exception "message" equals "Error message was changed" for request 1
+0
-19
features/negroni_features/recover.feature less more
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6
7 Scenario Outline: An error report and session is sent when request crashes but is recovered
8 When I start the service "negroni"
9 And I wait for the app to open port "4514"
10 And I wait for 2 seconds
11 And I open the URL "http://localhost:4514/recover"
12 Then I wait to receive 2 requests
13 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
14 And the exception "errorClass" equals "*errors.errorString" for request 0
15 And the exception "message" equals "Request killed but recovered" for request 0
16 And the request 1 is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the event handled sessions count equals 1 for request 0
18 And the number of sessions started equals 1 for request 1
+0
-27
features/negroni_features/releasestage.feature less more
0 Feature: Configuring release stage
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6 And I set environment variable "RELEASE_STAGE" to "my-stage"
7
8 Scenario: An error report is sent with configured release stage
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "negroni"
11 And I wait for the app to open port "4514"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4514/handled"
14 Then I wait to receive a request
15 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.releaseStage" equals "my-stage" for request 0
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "negroni"
21 And I wait for the app to open port "4514"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4514/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.releaseStage" equals "my-stage"
+0
-20
features/negroni_features/request.feature less more
0 Feature: Capturing request information automatically
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6
7 Scenario: An error report will automatically contain request information
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "negroni"
10 And I wait for the app to open port "4514"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4514/handled"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "request.clientIp" is not null
16 And the event "request.headers.User-Agent" equals "Ruby"
17 And the event "request.httpMethod" equals "GET"
18 And the event "request.url" ends with "/handled"
19 And the event "request.url" starts with "http://"
+0
-21
features/negroni_features/user.feature less more
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4514"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I start the service "negroni"
13 And I wait for the app to open port "4514"
14 And I wait for 2 seconds
15 And I open the URL "http://localhost:4514/user"
16 Then I wait to receive a request
17 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
18 And the event "user.id" equals "test-user-id"
19 And the event "user.name" equals "test-user-name"
20 And the event "user.email" equals "test-user-email"
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I set environment variable "SERVER_PORT" to "4512"
7
8 Scenario: A error report contains the configured app type when using a net http app
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "nethttp"
11 And I wait for the app to open port "4512"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4512/handled"
14 Then I wait to receive a request
15 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.version" equals "3.1.2"
17
18 Scenario: A session report contains the configured app type when using a net http app
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "nethttp"
21 And I wait for the app to open port "4512"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4512/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.version" equals "3.1.2"
27
0 Feature: Configure auto capture sessions
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6
7 Scenario: A session is not sent if auto capture sessions is off
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/session"
13 And I wait for 2 seconds
14 Then I should receive no requests
15
16 Scenario: A session is sent if auto capture sessions is on
17 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I start the service "nethttp"
19 And I wait for the app to open port "4512"
20 And I wait for 2 seconds
21 And I open the URL "http://localhost:4512/session"
22 Then I wait to receive a request after the start up session
23 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6
7 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/autonotify-then-recover"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is true
16 And the exception "errorClass" equals "*runtime.TypeAssertionError"
17 And the exception "message" matches "interface conversion: interface ({} )?is struct {}, not string"
18
19 Scenario: An error report is sent when a go routine crashes which is reported through auto notify
20 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
21 When I start the service "nethttp"
22 And I wait for the app to open port "4512"
23 And I wait for 2 seconds
24 And I open the URL "http://localhost:4512/autonotify"
25 Then I wait to receive a request
26 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
27 And the event "unhandled" is true
28 And the exception "errorClass" equals "*errors.errorString"
29 And the exception "message" equals "Go routine killed with auto notify"
0 Feature: Handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/handled"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is false for request 0
16 And the event "severity" equals "warning" for request 0
17 And the event "severityReason.type" equals "handledError" for request 0
18 And the exception is a PathError for request 0
19 And the "file" of stack frame 0 equals "main.go" for request 0
20
21 Scenario: A handled error sends a report with a custom name
22 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
23 When I start the service "nethttp"
24 And I wait for the app to open port "4512"
25 And I wait for 2 seconds
26 And I open the URL "http://localhost:4512/handled"
27 Then I wait to receive a request
28 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is false for request 0
30 And the event "severity" equals "warning" for request 0
31 And the event "severityReason.type" equals "handledError" for request 0
32 And the exception "errorClass" equals "MyCustomErrorClass" for request 0
33 And the "file" of stack frame 0 equals "main.go" for request 0
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/onbeforenotify"
13 Then I wait to receive 2 requests
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "message" equals "Don't ignore this error" for request 0
16 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the exception "message" equals "Error message was changed" for request 1
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report is sent when request crashes but is recovered
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/recover"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "errorClass" equals "*errors.errorString" for request 0
16 And the exception "message" equals "Request killed but recovered" for request 0
0 Feature: Configuring release stage
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "RELEASE_STAGE" to "my-stage"
7
8 Scenario: An error report is sent with configured release stage
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "nethttp"
11 And I wait for the app to open port "4512"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4512/handled"
14 Then I wait to receive a request
15 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.releaseStage" equals "my-stage" for request 0
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "nethttp"
21 And I wait for the app to open port "4512"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4512/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.releaseStage" equals "my-stage"
0 Feature: Capturing request information automatically
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6
7 Scenario: An error report will automatically contain request information
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/handled"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "request.clientIp" is not null
16 And the event "request.headers.User-Agent" equals "Ruby"
17 And the event "request.httpMethod" equals "GET"
18 And the event "request.url" ends with "/handled"
19 And the event "request.url" starts with "http://"
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I start the service "nethttp"
13 And I wait for the app to open port "4512"
14 And I wait for 2 seconds
15 And I open the URL "http://localhost:4512/user"
16 Then I wait to receive a request
17 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
18 And the event "user.id" equals "test-user-id"
19 And the event "user.name" equals "test-user-name"
20 And the event "user.email" equals "test-user-email"
+0
-28
features/net_http_features/appversion.feature less more
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I set environment variable "SERVER_PORT" to "4512"
7
8 Scenario: A error report contains the configured app type when using a net http app
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "nethttp"
11 And I wait for the app to open port "4512"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4512/handled"
14 Then I wait to receive a request
15 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.version" equals "3.1.2"
17
18 Scenario: A session report contains the configured app type when using a net http app
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "nethttp"
21 And I wait for the app to open port "4512"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4512/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.version" equals "3.1.2"
27
+0
-24
features/net_http_features/autocapturesessions.feature less more
0 Feature: Configure auto capture sessions
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6
7 Scenario: A session is not sent if auto capture sessions is off
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/session"
13 And I wait for 2 seconds
14 Then I should receive no requests
15
16 Scenario: A session is sent if auto capture sessions is on
17 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I start the service "nethttp"
19 And I wait for the app to open port "4512"
20 And I wait for 2 seconds
21 And I open the URL "http://localhost:4512/session"
22 Then I wait to receive a request after the start up session
23 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
+0
-30
features/net_http_features/autonotify.feature less more
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6
7 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/autonotify-then-recover"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is true
16 And the exception "errorClass" equals "*runtime.TypeAssertionError"
17 And the exception "message" matches "interface conversion: interface ({} )?is struct {}, not string"
18
19 Scenario: An error report is sent when a go routine crashes which is reported through auto notify
20 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
21 When I start the service "nethttp"
22 And I wait for the app to open port "4512"
23 And I wait for 2 seconds
24 And I open the URL "http://localhost:4512/autonotify"
25 Then I wait to receive a request
26 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
27 And the event "unhandled" is true
28 And the exception "errorClass" equals "*errors.errorString"
29 And the exception "message" equals "Go routine killed with auto notify"
+0
-34
features/net_http_features/handled.feature less more
0 Feature: Handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/handled"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is false for request 0
16 And the event "severity" equals "warning" for request 0
17 And the event "severityReason.type" equals "handledError" for request 0
18 And the exception "errorClass" equals "*os.PathError" for request 0
19 And the "file" of stack frame 0 equals "main.go" for request 0
20
21 Scenario: A handled error sends a report with a custom name
22 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
23 When I start the service "nethttp"
24 And I wait for the app to open port "4512"
25 And I wait for 2 seconds
26 And I open the URL "http://localhost:4512/handled"
27 Then I wait to receive a request
28 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is false for request 0
30 And the event "severity" equals "warning" for request 0
31 And the event "severityReason.type" equals "handledError" for request 0
32 And the exception "errorClass" equals "MyCustomErrorClass" for request 0
33 And the "file" of stack frame 0 equals "main.go" for request 0
+0
-18
features/net_http_features/onbeforenotify.feature less more
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/onbeforenotify"
13 Then I wait to receive 2 requests
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "message" equals "Don't ignore this error" for request 0
16 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the exception "message" equals "Error message was changed" for request 1
+0
-17
features/net_http_features/recover.feature less more
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report is sent when request crashes but is recovered
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/recover"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "errorClass" equals "*errors.errorString" for request 0
16 And the exception "message" equals "Request killed but recovered" for request 0
+0
-27
features/net_http_features/releasestage.feature less more
0 Feature: Configuring release stage
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "RELEASE_STAGE" to "my-stage"
7
8 Scenario: An error report is sent with configured release stage
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "nethttp"
11 And I wait for the app to open port "4512"
12 And I wait for 2 seconds
13 And I open the URL "http://localhost:4512/handled"
14 Then I wait to receive a request
15 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.releaseStage" equals "my-stage" for request 0
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "nethttp"
21 And I wait for the app to open port "4512"
22 And I wait for 2 seconds
23 And I open the URL "http://localhost:4512/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.releaseStage" equals "my-stage"
+0
-20
features/net_http_features/request.feature less more
0 Feature: Capturing request information automatically
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6
7 Scenario: An error report will automatically contain request information
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "nethttp"
10 And I wait for the app to open port "4512"
11 And I wait for 2 seconds
12 And I open the URL "http://localhost:4512/handled"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "request.clientIp" is not null
16 And the event "request.headers.User-Agent" equals "Ruby"
17 And the event "request.httpMethod" equals "GET"
18 And the event "request.url" ends with "/handled"
19 And the event "request.url" starts with "http://"
+0
-21
features/net_http_features/user.feature less more
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4512"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I start the service "nethttp"
13 And I wait for the app to open port "4512"
14 And I wait for 2 seconds
15 And I open the URL "http://localhost:4512/user"
16 Then I wait to receive a request
17 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
18 And the event "user.id" equals "test-user-id"
19 And the event "user.name" equals "test-user-name"
20 And the event "user.email" equals "test-user-email"
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
8 When I run the go service "app" with the test case "onbeforenotify"
9 Then I wait to receive 2 requests after the start up session
10 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11 And the exception "message" equals "Don't ignore this error" for request 0
12 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the exception "message" equals "Error message was changed" for request 1
0 Feature: Configuring param filters
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report containing meta data is not filtered when the param filters are set but do not match
9 Given I set environment variable "PARAMS_FILTERS" to "Name"
10 When I run the go service "app" with the test case "filtered"
11 Then I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "metaData.Account.Price(dollars)" equals "1 Million"
14
15 Scenario: An error report containing meta data is filtered when the param filters are set and completely match
16 Given I set environment variable "PARAMS_FILTERS" to "Price(dollars)"
17 When I run the go service "app" with the test case "filtered"
18 Then I wait to receive a request
19 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the event "metaData.Account.Price(dollars)" equals "[FILTERED]"
21
22 Scenario: An error report containing meta data is filtered when the param filters are set and partially match
23 Given I set environment variable "PARAMS_FILTERS" to "Price"
24 When I run the go service "app" with the test case "filtered"
25 Then I wait to receive a request
26 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
27 And the event "metaData.Account.Price(dollars)" equals "[FILTERED]"
28
+0
-22
features/plain_features/apptype.feature less more
0 Feature: Configuring app type
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_TYPE" to "background-queue"
6 And I have built the service "app"
7
8 Scenario: An error report contains the configured app type when running a go app
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I run the go service "app" with the test case "handled"
11 Then I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "app.type" equals "background-queue"
14
15 Scenario: An session report contains the configured app type when running a go app
16 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
17 When I run the go service "app" with the test case "session"
18 Then I wait to receive a request after the start up session
19 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the payload field "app.type" equals "background-queue"
21
+0
-21
features/plain_features/appversion.feature less more
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I have built the service "app"
7
8 Scenario: An error report contains the configured app type when running a go app
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I run the go service "app" with the test case "handled"
11 Then I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "app.version" equals "3.1.2"
14
15 Scenario: An session report contains the configured app type when running a go app
16 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
17 When I run the go service "app" with the test case "session"
18 Then I wait to receive a request after the start up session
19 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the payload field "app.version" equals "3.1.2"
+0
-13
features/plain_features/autonotify.feature less more
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
8 When I run the go service "app" with the test case "autonotify"
9 Then I wait for 3 seconds
10 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11 And the exception "errorClass" equals "*errors.errorString" for request 1
12 And the exception "message" equals "Go routine killed with auto notify" for request 1
+0
-21
features/plain_features/endpoint.feature less more
0 Feature: Configuring endpoint
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report is sent successfully using the legacy endpoint
8 When I run the go service "app" with the test case "endpoint legacy"
9 Then I wait to receive a request
10 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11
12 Scenario: An error report is sent successfully using the notify endpoint only
13 When I run the go service "app" with the test case "endpoint notify"
14 Then I wait to receive a request
15 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16
17 Scenario: Configuring Bugsnag will panic if the sessions endpoint is configured without the notify endpoint
18 When I run the go service "app" with the test case "endpoint session"
19 And I wait for 3 second
20 Then I should receive no requests
+0
-52
features/plain_features/handled.feature less more
0 Feature: Plain handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I run the go service "app" with the test case "handled"
10 Then I wait to receive a request
11 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the event "unhandled" is false
13 And the event "severity" equals "warning"
14 And the event "severityReason.type" equals "handledError"
15 And the exception "errorClass" equals "*os.PathError"
16 And the "file" of stack frame 0 equals "main.go"
17
18 Scenario: A handled error sends a report with a custom name
19 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
20 When I run the go service "app" with the test case "handled"
21 Then I wait to receive a request
22 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
23 And the event "unhandled" is false
24 And the event "severity" equals "warning"
25 And the event "severityReason.type" equals "handledError"
26 And the exception "errorClass" equals "MyCustomErrorClass"
27 And the "file" of stack frame 0 equals "main.go"
28
29 Scenario: Sending an event using a callback to modify report contents
30 When I run the go service "app" with the test case "handled with callback"
31 Then I wait to receive a request
32 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
33 And the event "unhandled" is false
34 And the event "severity" equals "info"
35 And the event "severityReason.type" equals "userCallbackSetSeverity"
36 And the event "context" equals "nonfatal.go:14"
37 And the "file" of stack frame 0 equals "main.go"
38 And stack frame 0 contains a local function spanning 240 to 246
39 And the "file" of stack frame 1 equals ">insertion<"
40 And the "lineNumber" of stack frame 1 equals 0
41
42 Scenario: Marking an error as unhandled in a callback
43 When I run the go service "app" with the test case "make unhandled with callback"
44 Then I wait to receive a request
45 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
46 And the event "unhandled" is true
47 And the event "severity" equals "error"
48 And the event "severityReason.type" equals "userCallbackSetSeverity"
49 And the event "severityReason.unhandledOverridden" is true
50 And the "file" of stack frame 0 equals "main.go"
51 And stack frame 0 contains a local function spanning 252 to 255
+0
-22
features/plain_features/hostname.feature less more
0 Feature: Configuring hostname
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report contains the configured hostname
8 Given I set environment variable "HOSTNAME" to "server-1a"
9 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I run the go service "app" with the test case "handled"
11 And I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "device.hostname" equals "server-1a"
14
15 Scenario: An session report contains the configured hostname
16 Given I set environment variable "HOSTNAME" to "server-1a"
17 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I run the go service "app" with the test case "session"
19 And I wait to receive a request after the start up session
20 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
21 And the payload field "device.hostname" equals "server-1a"
+0
-15
features/plain_features/metadata.feature less more
0 Feature: Sending meta data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report contains custom meta data
9 When I run the go service "app" with the test case "metadata"
10 And I wait to receive a request
11 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the event "metaData.Scheme.Customer.Name" equals "Joe Bloggs"
13 And the event "metaData.Scheme.Customer.Age" equals "21"
14 And the event "metaData.Scheme.Level" equals "Blue"
+0
-23
features/plain_features/multieventsession.feature less more
0 Feature: Reporting multiple handled and unhandled errors in the same session
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Handled errors know about previous reported handled errors
9 When I run the go service "app" with the test case "multiple handled"
10 And I wait to receive 2 requests
11 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event handled sessions count equals 1 for request 0
14 And the event handled sessions count equals 2 for request 1
15
16 Scenario: Unhandled errors know about previous reported handled errors
17 When I run the go service "app" with the test case "multiple unhandled"
18 And I wait to receive 2 requests
19 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
21 And the event unhandled sessions count equals 1 for request 0
22 And the event unhandled sessions count equals 2 for request 1
+0
-14
features/plain_features/onbeforenotify.feature less more
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
8 When I run the go service "app" with the test case "onbeforenotify"
9 Then I wait to receive 2 requests after the start up session
10 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11 And the exception "message" equals "Don't ignore this error" for request 0
12 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the exception "message" equals "Error message was changed" for request 1
0 Feature: Panic handling
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Capturing a panic
9 When I run the go service "app" with the test case "unhandled"
10 Then I wait to receive a request
11 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the event "unhandled" is true
13 And the event "severity" equals "error"
14 And the event "severityReason.type" equals "unhandledPanic"
15 And the exception "errorClass" equals "panic"
16 And the exception "message" is one of:
17 | interface conversion: interface is struct {}, not string |
18 | interface conversion: interface {} is struct {}, not string |
19 And the in-project frames of the stacktrace are:
20 | file | method |
21 | main.go | unhandledCrash.func1 |
22 | main.go | unhandledCrash |
+0
-29
features/plain_features/paramfilters.feature less more
0 Feature: Configuring param filters
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report containing meta data is not filtered when the param filters are set but do not match
9 Given I set environment variable "PARAMS_FILTERS" to "Name"
10 When I run the go service "app" with the test case "filtered"
11 Then I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "metaData.Account.Price(dollars)" equals "1 Million"
14
15 Scenario: An error report containing meta data is filtered when the param filters are set and completely match
16 Given I set environment variable "PARAMS_FILTERS" to "Price(dollars)"
17 When I run the go service "app" with the test case "filtered"
18 Then I wait to receive a request
19 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
20 And the event "metaData.Account.Price(dollars)" equals "[FILTERED]"
21
22 Scenario: An error report containing meta data is filtered when the param filters are set and partially match
23 Given I set environment variable "PARAMS_FILTERS" to "Price"
24 When I run the go service "app" with the test case "filtered"
25 Then I wait to receive a request
26 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
27 And the event "metaData.Account.Price(dollars)" equals "[FILTERED]"
28
+0
-14
features/plain_features/recover.feature less more
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report is sent when a go routine crashes but recovers
9 When I run the go service "app" with the test case "recover"
10 Then I wait to receive a request
11 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the exception "errorClass" equals "*errors.errorString"
13 And the exception "message" equals "Go routine killed but recovered"
+0
-71
features/plain_features/releasestage.feature less more
0 Feature: Configuring release stages and notify release stages
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report is sent when release stage matches notify release stages
8 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
9 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 And I set environment variable "RELEASE_STAGE" to "stage2"
11 When I run the go service "app" with the test case "handled"
12 Then I wait to receive a request
13 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
14 And the event "app.releaseStage" equals "stage2"
15
16 Scenario: An error report is sent when no notify release stages are specified
17 Given I set environment variable "RELEASE_STAGE" to "stage2"
18 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
19 When I run the go service "app" with the test case "handled"
20 Then I wait to receive a request
21 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
22 And the event "app.releaseStage" equals "stage2"
23
24 Scenario: An error report is sent regardless of notify release stages if release stage is not set
25 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
26 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
27 When I run the go service "app" with the test case "handled"
28 Then I wait to receive a request
29 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
30
31 Scenario: An error report is not sent if the release stage does not match the notify release stages
32 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
33 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
34 And I set environment variable "RELEASE_STAGE" to "stage4"
35 When I run the go service "app" with the test case "handled"
36 And I wait for 3 second
37 Then I should receive no requests
38
39 Scenario: An session report is sent when release stage matches notify release stages
40 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
41 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
42 And I set environment variable "RELEASE_STAGE" to "stage2"
43 When I run the go service "app" with the test case "session"
44 Then I wait to receive a request after the start up session
45 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
46 And the payload field "app.releaseStage" equals "stage2"
47
48 Scenario: An session report is sent when no notify release stages are specified
49 Given I set environment variable "RELEASE_STAGE" to "stage2"
50 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
51 When I run the go service "app" with the test case "session"
52 Then I wait to receive a request after the start up session
53 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
54 And the payload field "app.releaseStage" equals "stage2"
55
56 Scenario: An session report is sent regardless of notify release stages if release stage is not set
57 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
58 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
59 When I run the go service "app" with the test case "session"
60 Then I wait to receive a request after the start up session
61 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
62
63 Scenario: An session report is not sent if the release stage does not match the notify release stages
64 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
65 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
66 And I set environment variable "RELEASE_STAGE" to "stage4"
67 When I run the go service "app" with the test case "session"
68 And I wait for 3 second
69 Then I should receive no requests
70
+0
-15
features/plain_features/sessioncontext.feature less more
0 Feature: Session data inside an error report using a session context
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report contains a session count when part of a session
8 When I run the go service "app" with the test case "session and error"
9 Then I wait to receive 2 requests after the start up session
10 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11 And the request 1 is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the event handled sessions count equals 1 for request 0
13 And the event unhandled sessions count equals 0 for request 0
14 And the number of sessions started equals 1 for request 1
+0
-20
features/plain_features/synchronous.feature less more
0 Feature: Configuring synchronous flag
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report is sent asynchrously but exits immediately so is not sent
9 Given I set environment variable "SYNCHRONOUS" to "false"
10 When I run the go service "app" with the test case "send and exit"
11 And I wait for 3 second
12 Then I should receive no requests
13
14 Scenario: An error report is report synchronously so it will send before exiting
15 Given I set environment variable "SYNCHRONOUS" to "true"
16 When I run the go service "app" with the test case "send and exit"
17 Then I wait to receive 1 requests
18 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
19
+0
-18
features/plain_features/user.feature less more
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I run the go service "app" with the test case "user"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "user.id" equals "test-user-id"
16 And the event "user.name" equals "test-user-name"
17 And the event "user.email" equals "test-user-email"
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report is sent when a go routine crashes but recovers
9 When I run the go service "app" with the test case "recover"
10 Then I wait to receive a request
11 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the exception "errorClass" equals "*errors.errorString"
13 And the exception "message" equals "Go routine killed but recovered"
0 Feature: Configuring release stages and notify release stages
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report is sent when release stage matches notify release stages
8 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
9 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 And I set environment variable "RELEASE_STAGE" to "stage2"
11 When I run the go service "app" with the test case "handled"
12 Then I wait to receive a request
13 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
14 And the event "app.releaseStage" equals "stage2"
15
16 Scenario: An error report is sent when no notify release stages are specified
17 Given I set environment variable "RELEASE_STAGE" to "stage2"
18 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
19 When I run the go service "app" with the test case "handled"
20 Then I wait to receive a request
21 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
22 And the event "app.releaseStage" equals "stage2"
23
24 Scenario: An error report is sent regardless of notify release stages if release stage is not set
25 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
26 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
27 When I run the go service "app" with the test case "handled"
28 Then I wait to receive a request
29 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
30
31 Scenario: An error report is not sent if the release stage does not match the notify release stages
32 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
33 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
34 And I set environment variable "RELEASE_STAGE" to "stage4"
35 When I run the go service "app" with the test case "handled"
36 And I wait for 3 second
37 Then I should receive no requests
38
39 Scenario: An session report is sent when release stage matches notify release stages
40 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
41 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
42 And I set environment variable "RELEASE_STAGE" to "stage2"
43 When I run the go service "app" with the test case "session"
44 Then I wait to receive a request after the start up session
45 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
46 And the payload field "app.releaseStage" equals "stage2"
47
48 Scenario: An session report is sent when no notify release stages are specified
49 Given I set environment variable "RELEASE_STAGE" to "stage2"
50 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
51 When I run the go service "app" with the test case "session"
52 Then I wait to receive a request after the start up session
53 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
54 And the payload field "app.releaseStage" equals "stage2"
55
56 Scenario: An session report is sent regardless of notify release stages if release stage is not set
57 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
58 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
59 When I run the go service "app" with the test case "session"
60 Then I wait to receive a request after the start up session
61 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
62
63 Scenario: An session report is not sent if the release stage does not match the notify release stages
64 Given I set environment variable "NOTIFY_RELEASE_STAGES" to "stage1,stage2,stage3"
65 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
66 And I set environment variable "RELEASE_STAGE" to "stage4"
67 When I run the go service "app" with the test case "session"
68 And I wait for 3 second
69 Then I should receive no requests
70
+0
-27
features/revel_features/appversion.feature less more
0 Feature: Configuring app version
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "APP_VERSION" to "3.1.2"
6 And I set environment variable "SERVER_PORT" to "4515"
7
8 Scenario: A error report contains the configured app type
9 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
10 When I start the service "revel"
11 And I wait for the app to open port "4515"
12 And I wait for 4 seconds
13 And I open the URL "http://localhost:4515/handled"
14 Then I wait to receive a request
15 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
16 And the event "app.version" equals "3.1.2"
17
18 Scenario: A session report contains the configured app type
19 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
20 When I start the service "revel"
21 And I wait for the app to open port "4515"
22 And I wait for 4 seconds
23 And I open the URL "http://localhost:4515/session"
24 Then I wait to receive a request after the start up session
25 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
26 And the payload field "app.version" equals "3.1.2"
+0
-24
features/revel_features/autocapturesessions.feature less more
0 Feature: Configure auto capture sessions
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6
7 Scenario: A session is not sent if auto capture sessions is off
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "revel"
10 And I wait for the app to open port "4515"
11 And I wait for 4 seconds
12 And I open the URL "http://localhost:4515/session"
13 And I wait for 2 seconds
14 Then I should receive no requests
15
16 Scenario: A session is sent if auto capture sessions is on
17 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "true"
18 When I start the service "revel"
19 And I wait for the app to open port "4515"
20 And I wait for 4 seconds
21 And I open the URL "http://localhost:4515/session"
22 Then I wait to receive a request after the start up session
23 And the request is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
+0
-33
features/revel_features/autonotify.feature less more
0 Feature: Using auto notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6
7 Scenario: An error report is sent when an AutoNotified crash occurs which later gets recovered
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "revel"
10 And I wait for the app to open port "4515"
11 And I wait for 4 seconds
12 And I open the URL "http://localhost:4515/autonotify-then-recover"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is true
16 And the event "severityReason.type" equals "unhandledErrorMiddleware" for request 0
17 And the event "severityReason.attributes.framework" equals "Revel" for request 0
18 And the exception "errorClass" equals "*runtime.TypeAssertionError"
19 And the exception "message" matches "interface conversion: interface ({} )?is struct {}, not string"
20
21 Scenario: An error report is sent when a go routine crashes which is reported through auto notify
22 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
23 When I start the service "revel"
24 And I wait for the app to open port "4515"
25 And I wait for 4 seconds
26 And I open the URL "http://localhost:4515/autonotify"
27 Then I wait to receive a request
28 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is true
30 And the event "severityReason.type" equals "handledPanic" for request 0
31 And the exception "errorClass" equals "*errors.errorString"
32 And the exception "message" equals "Go routine killed with auto notify"
+0
-17
features/revel_features/configfile.feature less more
0 Feature: Configuring using the config file
1
2 Background:
3 Given I configure the bugsnag endpoint
4 And I set environment variable "USE_PROPERTIES_FILE_CONFIG" to "true"
5
6 Scenario: A error report contains the variables set in the config file
7 When I start the service "revel"
8 And I wait for the app to open port "4515"
9 And I wait for 4 seconds
10 And I open the URL "http://localhost:4515/handled"
11 Then I wait to receive a request
12 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
13 And the event "app.version" equals "4.5.6"
14 And the event "app.type" equals "config-file-app-type"
15 And the event "app.releaseStage" equals "config-test"
16 And the event "device.hostname" equals "config-file-server"
+0
-34
features/revel_features/handled.feature less more
0 Feature: Handled errors
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: A handled error sends a report
9 When I start the service "revel"
10 And I wait for the app to open port "4515"
11 And I wait for 4 seconds
12 And I open the URL "http://localhost:4515/handled"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "unhandled" is false for request 0
16 And the event "severity" equals "warning" for request 0
17 And the event "severityReason.type" equals "handledError" for request 0
18 And the exception "errorClass" equals "*os.PathError" for request 0
19 And the "file" of stack frame 0 equals "controllers/app.go" for request 0
20
21 Scenario: A handled error sends a report with a custom name
22 Given I set environment variable "ERROR_CLASS" to "MyCustomErrorClass"
23 When I start the service "revel"
24 And I wait for the app to open port "4515"
25 And I wait for 4 seconds
26 And I open the URL "http://localhost:4515/handled"
27 Then I wait to receive a request
28 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
29 And the event "unhandled" is false for request 0
30 And the event "severity" equals "warning" for request 0
31 And the event "severityReason.type" equals "handledError" for request 0
32 And the exception "errorClass" equals "MyCustomErrorClass" for request 0
33 And the "file" of stack frame 0 equals "controllers/app.go" for request 0
+0
-18
features/revel_features/onbeforenotify.feature less more
0 Feature: Configuring on before notify
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: Send three bugsnags and use on before notify to drop one and modify the message of another
9 When I start the service "revel"
10 And I wait for the app to open port "4515"
11 And I wait for 4 seconds
12 And I open the URL "http://localhost:4515/onbeforenotify"
13 Then I wait to receive 2 requests
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "message" equals "Don't ignore this error" for request 0
16 And the request 1 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the exception "message" equals "Error message was changed" for request 1
+0
-17
features/revel_features/recover.feature less more
0 Feature: Using recover
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report is sent when request crashes but is recovered
9 When I start the service "revel"
10 And I wait for the app to open port "4515"
11 And I wait for 4 seconds
12 And I open the URL "http://localhost:4515/recover"
13 Then I wait to receive a request
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the exception "errorClass" equals "*errors.errorString"
16 And the exception "message" equals "Request killed but recovered"
+0
-20
features/revel_features/releasestage.feature less more
0 Feature: Configuring release stage
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6
7 Scenario Outline: An error report and session is sent with configured release stage
8 Given I set environment variable "RELEASE_STAGE" to "my-stage"
9 When I start the service "revel"
10 And I wait for the app to open port "4515"
11 And I wait for 4 seconds
12 And I open the URL "http://localhost:4515/handled"
13 Then I wait to receive 2 requests
14 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "app.releaseStage" equals "my-stage" for request 0
16 And the request 1 is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
17 And the payload field "app.releaseStage" equals "my-stage" for request 1
18 And the event handled sessions count equals 1 for request 0
19 And the number of sessions started equals 1 for request 1
+0
-20
features/revel_features/request.feature less more
0 Feature: Capturing request information automatically
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6
7 Scenario: An error report will automatically contain request information
8 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
9 When I start the service "revel"
10 And I wait for the app to open port "4515"
11 And I wait for 4 seconds
12 And I open the URL "http://localhost:4515/handled"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "request.clientIp" is not null
16 And the event "request.headers.User-Agent" equals "Ruby"
17 And the event "request.httpMethod" equals "GET"
18 And the event "request.url" ends with "/handled"
19 And the event "request.url" starts with "http://"
+0
-21
features/revel_features/user.feature less more
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I set environment variable "SERVER_PORT" to "4515"
6 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I start the service "revel"
13 And I wait for the app to open port "4515"
14 And I wait for 4 seconds
15 And I open the URL "http://localhost:4515/user"
16 Then I wait to receive a request
17 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
18 And the event "user.id" equals "test-user-id"
19 And the event "user.name" equals "test-user-name"
20 And the event "user.email" equals "test-user-email"
+0
-67
features/run-maze.sh less more
0 #!/bin/sh
1
2 set -e
3
4 bundle install
5
6 function plain() {
7 echo "Running maze tests for plain Go apps"
8 bundle exec bugsnag-maze-runner features/plain_features
9 }
10
11 function nethttp() {
12 echo "Running maze tests for net/http"
13 bundle exec bugsnag-maze-runner features/net_http_features
14 }
15
16 function martini() {
17 echo "Running maze tests for Martini"
18 bundle exec bugsnag-maze-runner features/martini_features
19 }
20
21 function gin() {
22 echo "Running maze tests for Gin version $1"
23 GIN_VERSION=$1 bundle exec bugsnag-maze-runner features/gin_features
24 }
25
26 function negroni() {
27 echo "Running maze tests for Negroni version $1"
28 NEGRONI_VERSION=$1 bundle exec bugsnag-maze-runner features/negroni_features
29 }
30
31 function revel() {
32 echo "Running maze tests for Revel version $1 and CMD version $2"
33 REVEL_VERSION=$1 REVEL_CMD_VERSION=$2 bundle exec bugsnag-maze-runner features/revel_features --verbose
34 }
35
36 function run_all() {
37 export GO_VERSION=$1
38 echo "Running maze tests for Go version $GO_VERSION"
39
40 plain
41 nethttp
42 martini
43
44 gin v1.3.0
45 gin v1.2
46 gin v1.1
47 gin v1.0
48
49 negroni v1.0.0
50 negroni v0.3.0
51 negroni v0.2.0
52
53 # Revel requires Go 1.8 or higher
54 if [ $1 != "1.7" ] ; then
55 revel v0.21.0 v0.21.1
56 revel v0.20.0 v0.20.2
57 revel v0.19.1 v0.19.0
58 revel v0.18.0 v0.18.0
59 fi
60 }
61
62 run_all 1.11
63 run_all 1.10
64 run_all 1.9
65 run_all 1.8
66 run_all 1.7
0 Feature: Session data inside an error report using a session context
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I configure the bugsnag endpoint
5 And I have built the service "app"
6
7 Scenario: An error report contains a session count when part of a session
8 When I run the go service "app" with the test case "session-and-error"
9 Then I wait to receive 2 requests after the start up session
10 And the request 0 is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
11 And the request 1 is a valid session report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
12 And the event handled sessions count equals 1 for request 0
13 And the event unhandled sessions count equals 0 for request 0
14 And the number of sessions started equals 1 for request 1
00 require 'net/http'
1
2 Given("I set environment variable {string} to the app directory") do |key|
3 step("I set environment variable \"#{key}\" to \"/app/src/test/\"")
4 end
5
6 Given("I set environment variable {string} to the notify endpoint") do |key|
7 step("I set environment variable \"#{key}\" to \"http://#{current_ip}:#{MOCK_API_PORT}\"")
8 end
9
10 Given("I set environment variable {string} to the sessions endpoint") do |key|
11 # they're the same picture dot gif
12 # split them out for the future endpoint splitting work
13 step("I set environment variable \"#{key}\" to \"http://#{current_ip}:#{MOCK_API_PORT}\"")
14 end
115
216 Then(/^the request(?: (\d+))? is a valid error report with api key "(.*)"$/) do |request_index, api_key|
317 request_index ||= 0
620 And the "bugsnag-api-key" header equals "#{api_key}" for request #{request_index}
721 And the payload field "apiKey" equals "#{api_key}" for request #{request_index}
822 }
23 end
24
25 Then(/^the exception is a PathError for request (\d+)$/) do |request_index|
26 body = find_request(request_index)[:body]
27 error_class = body["events"][0]["exceptions"][0]["errorClass"]
28 if ['1.11', '1.12', '1.13', '1.14', '1.15'].include? ENV['GO_VERSION']
29 assert_equal(error_class, '*os.PathError')
30 else
31 assert_equal(error_class, '*fs.PathError')
32 end
933 end
1034
1135 Then(/^the request(?: (\d+))? is a valid session report with api key "(.*)"$/) do |request_index, api_key|
2953 end
3054
3155 When("I run the go service {string} with the test case {string}") do |service, testcase|
32 run_service_with_command(service, "go run main.go -test=\"#{testcase}\"")
56 run_service_with_command(service, "./run.sh go run . -test=\"#{testcase}\"")
3357 end
3458
3559 Then(/^I wait to receive a request after the start up session$/) do
6084 assert_equal(request_count, stored_requests.size, "#{stored_requests.size} requests received")
6185 end
6286
87 Then(/^(\d+) requests? (?:was|were) received$/) do |request_count|
88 sleep 1
89 assert_equal(request_count, stored_requests.size, "#{stored_requests.size} requests received")
90 end
91
92 Then("the in-project frames of the stacktrace are:") do |table|
93 body = find_request(0)[:body]
94 stacktrace = body["events"][0]["exceptions"][0]["stacktrace"]
95 found = 0 # counts matching frames and ensures ordering is correct
96 expected = table.hashes.length
97 stacktrace.each do |frame|
98 if found < expected and frame["inProject"] and
99 frame["file"] == table.hashes[found]["file"] and
100 frame["method"] == table.hashes[found]["method"]
101 found = found + 1
102 end
103 end
104 assert_equal(found, expected, "expected #{expected} matching frames but found #{found}. stacktrace:\n#{stacktrace}")
105 end
106
63107 Then("stack frame {int} contains a local function spanning {int} to {int}") do |frame, val, old_val|
64108 # Old versions of Go put the line number on the end of the function
65109 if ['1.7', '1.8'].include? ENV['GO_VERSION']
68112 step "the \"lineNumber\" of stack frame #{frame} equals #{val}"
69113 end
70114 end
115
116 Then("the exception {string} is one of:") do |key, table|
117 body = find_request(0)[:body]
118 exception = body["events"][0]["exceptions"][0]
119 options = table.raw.flatten
120 assert(options.include?(exception[key]), "expected '#{key}' to be one of #{options}")
121 end
77 Dir.mkdir testBuildFolder
88
99 # Copy the existing dir
10 `find . -name '*.go' \
10 `find . -name '*.go' -o -name 'go.sum' -o -name 'go.mod' \
1111 -not -path "./examples/*" \
1212 -not -path "./testutil/*" \
13 -not -path "./v2/testutil/*" \
1314 -not -path "./features/*" \
1415 -not -name '*_test.go' | cpio -pdm #{testBuildFolder}`
0 Feature: Configuring synchronous flag
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 And I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report is sent asynchrously but exits immediately so is not sent
9 Given I set environment variable "SYNCHRONOUS" to "false"
10 When I run the go service "app" with the test case "send-and-exit"
11 And I wait for 3 second
12 Then I should receive no requests
13
14 Scenario: An error report is report synchronously so it will send before exiting
15 Given I set environment variable "SYNCHRONOUS" to "true"
16 When I run the go service "app" with the test case "send-and-exit"
17 Then I wait to receive 1 requests
18 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
19
0 Feature: Sending user data
1
2 Background:
3 Given I set environment variable "API_KEY" to "a35a2a72bd230ac0aa0f52715bbdc6aa"
4 Given I set environment variable "AUTO_CAPTURE_SESSIONS" to "false"
5 And I configure the bugsnag endpoint
6 And I have built the service "app"
7
8 Scenario: An error report contains custom user data
9 Given I set environment variable "USER_ID" to "test-user-id"
10 And I set environment variable "USER_NAME" to "test-user-name"
11 And I set environment variable "USER_EMAIL" to "test-user-email"
12 When I run the go service "app" with the test case "user"
13 Then I wait to receive a request
14 And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
15 And the event "user.id" equals "test-user-id"
16 And the event "user.name" equals "test-user-name"
17 And the event "user.email" equals "test-user-email"
7676 RuntimeVersions: device.GetRuntimeVersions(),
7777 },
7878 Request: p.Request,
79 Exceptions: []exceptionJSON{
80 exceptionJSON{
81 ErrorClass: p.ErrorClass,
82 Message: p.Message,
83 Stacktrace: p.Stacktrace,
84 },
85 },
79 Exceptions: p.exceptions(),
8680 GroupingHash: p.GroupingHash,
8781 Metadata: p.MetaData.sanitize(p.ParamsFilters),
8882 PayloadVersion: notifyPayloadVersion,
139133 }
140134 return nil
141135 }
136
137 func (p *payload) exceptions() []exceptionJSON {
138 exceptions := []exceptionJSON{
139 exceptionJSON{
140 ErrorClass: p.ErrorClass,
141 Message: p.Message,
142 Stacktrace: p.Stacktrace,
143 },
144 }
145
146 if p.Error == nil {
147 return exceptions
148 }
149
150 cause := p.Error.Cause
151 for cause != nil {
152 exceptions = append(exceptions, exceptionJSON{
153 ErrorClass: cause.TypeName(),
154 Message: cause.Error(),
155 Stacktrace: generateStacktrace(cause, p.Configuration),
156 })
157 cause = cause.Cause
158 }
159
160 return exceptions
161 }
1010 "github.com/bugsnag/bugsnag-go/sessions"
1111 )
1212
13 const expSmall = `{"apiKey":"","events":[{"app":{"releaseStage":""},"device":{"osName":"%s","runtimeVersions":{"go":"%s"}},"exceptions":[{"errorClass":"","message":"","stacktrace":null}],"metaData":{},"payloadVersion":"4","severity":"","unhandled":false}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"1.7.0"}}`
13 const expSmall = `{"apiKey":"","events":[{"app":{"releaseStage":""},"device":{"osName":"%s","runtimeVersions":{"go":"%s"}},"exceptions":[{"errorClass":"","message":"","stacktrace":null}],"metaData":{},"payloadVersion":"4","severity":"","unhandled":false}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"1.9.0"}}`
1414
1515 // The large payload has a timestamp in it which makes it awkward to assert against.
1616 // Instead, assert that the timestamp property exist, along with the rest of the expected payload
1717 const expLargePre = `{"apiKey":"166f5ad3590596f9aa8d601ea89af845","events":[{"app":{"releaseStage":"mega-production","type":"gin","version":"1.5.3"},"context":"/api/v2/albums","device":{"hostname":"super.duper.site","osName":"%s","runtimeVersions":{"go":"%s"}},"exceptions":[{"errorClass":"error class","message":"error message goes here","stacktrace":[{"method":"doA","file":"a.go","lineNumber":65},{"method":"fetchB","file":"b.go","lineNumber":99,"inProject":true},{"method":"incrementI","file":"i.go","lineNumber":651}]}],"groupingHash":"custom grouping hash","metaData":{"custom tab":{"my key":"my value"}},"payloadVersion":"4","session":{"startedAt":"`
18 const expLargePost = `,"severity":"info","severityReason":{"type":"unhandledError","attributes":{"framework":"gin"}},"unhandled":true,"user":{"id":"1234baerg134","name":"Kool Kidz on da bus","email":"typo@busgang.com"}}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"1.7.0"}}`
18 const expLargePost = `,"severity":"info","severityReason":{"type":"unhandledError","attributes":{"framework":"gin"}},"unhandled":true,"user":{"id":"1234baerg134","name":"Kool Kidz on da bus","email":"typo@busgang.com"}}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"1.9.0"}}`
1919
2020 func TestMarshalEmptyPayload(t *testing.T) {
2121 sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
1616 )
1717
1818 const testAPIKey = "166f5ad3590596f9aa8d601ea89af845"
19 const testPublishInterval = time.Millisecond * 20
19 const testPublishInterval = time.Millisecond * 200
2020 const sessionsCount = 50000
2121
2222 func init() {
0 Copyright (c) 2014 Bugsnag
1
2 Permission is hereby granted, free of charge, to any person obtaining
3 a copy of this software and associated documentation files (the
4 "Software"), to deal in the Software without restriction, including
5 without limitation the rights to use, copy, modify, merge, publish,
6 distribute, sublicense, and/or sell copies of the Software, and to
7 permit persons to whom the Software is furnished to do so, subject to
8 the following conditions:
9
10 The above copyright notice and this permission notice shall be
11 included in all copies or substantial portions of the Software.
12
13 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 package bugsnag
1
2 import (
3 "context"
4 "fmt"
5 "log"
6 "net/http"
7 "os"
8 "path/filepath"
9 "runtime"
10 "sync"
11 "time"
12
13 "github.com/bugsnag/bugsnag-go/v2/device"
14 "github.com/bugsnag/bugsnag-go/v2/errors"
15 "github.com/bugsnag/bugsnag-go/v2/sessions"
16
17 // Fixes a bug with SHA-384 intermediate certs on some platforms.
18 // - https://github.com/bugsnag/bugsnag-go/issues/9
19 _ "crypto/sha512"
20 )
21
22 // Version defines the version of this Bugsnag notifier
23 const Version = "2.1.2"
24
25 var panicHandlerOnce sync.Once
26 var sessionTrackerOnce sync.Once
27 var readEnvConfigOnce sync.Once
28 var middleware middlewareStack
29
30 // Config is the configuration for the default bugsnag notifier.
31 var Config Configuration
32 var sessionTrackingConfig sessions.SessionTrackingConfiguration
33
34 // DefaultSessionPublishInterval defines how often sessions should be sent to
35 // Bugsnag.
36 // Deprecated: Exposed for developer sanity in testing. Modify at own risk.
37 var DefaultSessionPublishInterval = 60 * time.Second
38 var defaultNotifier = Notifier{&Config, nil}
39 var sessionTracker sessions.SessionTracker
40
41 // Configure Bugsnag. The only required setting is the APIKey, which can be
42 // obtained by clicking on "Settings" in your Bugsnag dashboard. This function
43 // is also responsible for installing the global panic handler, so it should be
44 // called as early as possible in your initialization process.
45 func Configure(config Configuration) {
46 // Load configuration from the environment, if any
47 readEnvConfigOnce.Do(Config.loadEnv)
48 Config.update(&config)
49 updateSessionConfig()
50 // Only do once in case the user overrides the default panichandler, and
51 // configures multiple times.
52 panicHandlerOnce.Do(Config.PanicHandler)
53 }
54
55 // StartSession creates new context from the context.Context instance with
56 // Bugsnag session data attached. Will start the session tracker if not already
57 // started
58 func StartSession(ctx context.Context) context.Context {
59 sessionTrackerOnce.Do(startSessionTracking)
60 return sessionTracker.StartSession(ctx)
61 }
62
63 // Notify sends an error.Error to Bugsnag along with the current stack trace.
64 // If at all possible, it is recommended to pass in a context.Context, e.g.
65 // from a http.Request or bugsnag.StartSession() as Bugsnag will be able to
66 // extract additional information in some cases. The rawData is used to send
67 // extra information along with the error. For example you can pass the current
68 // http.Request to Bugsnag to see information about it in the dashboard, or set
69 // the severity of the notification. For a detailed list of the information
70 // that can be extracted, see
71 // https://docs.bugsnag.com/platforms/go/reporting-handled-errors/
72 func Notify(err error, rawData ...interface{}) error {
73 if e := checkForEmptyError(err); e != nil {
74 return e
75 }
76 // Stripping one stackframe to not include this function in the stacktrace
77 // for a manual notification.
78 skipFrames := 1
79 return defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
80 }
81
82 // AutoNotify logs a panic on a goroutine and then repanics.
83 // It should only be used in places that have existing panic handlers further
84 // up the stack.
85 // Although it's not strictly enforced, it's highly recommended to pass a
86 // context.Context object that has at one-point been returned from
87 // bugsnag.StartSession. Doing so ensures your stability score remains accurate,
88 // and future versions of Bugsnag may extract more useful information from this
89 // context.
90 // The rawData is used to send extra information along with any
91 // panics that are handled this way.
92 // Usage:
93 // go func() {
94 // ctx := bugsnag.StartSession(context.Background())
95 // defer bugsnag.AutoNotify(ctx)
96 // // (possibly crashy code)
97 // }()
98 // See also: bugsnag.Recover()
99 func AutoNotify(rawData ...interface{}) {
100 if err := recover(); err != nil {
101 severity := defaultNotifier.getDefaultSeverity(rawData, SeverityError)
102 state := HandledState{SeverityReasonHandledPanic, severity, true, ""}
103 rawData = append([]interface{}{state}, rawData...)
104 // We strip the following stackframes as they don't add much info
105 // - runtime/$arch - e.g. runtime/asm_amd64.s#call32
106 // - runtime/panic.go#gopanic
107 // Panics have their own stacktrace, so no stripping of the current stack
108 skipFrames := 2
109 defaultNotifier.NotifySync(errors.New(err, skipFrames), true, rawData...)
110 sessionTracker.FlushSessions()
111 panic(err)
112 }
113 }
114
115 // Recover logs a panic on a goroutine and then recovers.
116 // Although it's not strictly enforced, it's highly recommended to pass a
117 // context.Context object that has at one-point been returned from
118 // bugsnag.StartSession. Doing so ensures your stability score remains accurate,
119 // and future versions of Bugsnag may extract more useful information from this
120 // context.
121 // The rawData is used to send extra information along with
122 // any panics that are handled this way
123 // Usage:
124 // go func() {
125 // ctx := bugsnag.StartSession(context.Background())
126 // defer bugsnag.Recover(ctx)
127 // // (possibly crashy code)
128 // }()
129 // If you wish that any panics caught by the call to Recover shall affect your
130 // stability score (it does not by default):
131 // go func() {
132 // ctx := bugsnag.StartSession(context.Background())
133 // defer bugsnag.Recover(ctx, bugsnag.HandledState{Unhandled: true})
134 // // (possibly crashy code)
135 // }()
136 // See also: bugsnag.AutoNotify()
137 func Recover(rawData ...interface{}) {
138 if err := recover(); err != nil {
139 severity := defaultNotifier.getDefaultSeverity(rawData, SeverityWarning)
140 state := HandledState{SeverityReasonHandledPanic, severity, false, ""}
141 rawData = append([]interface{}{state}, rawData...)
142 // We strip the following stackframes as they don't add much info
143 // - runtime/$arch - e.g. runtime/asm_amd64.s#call32
144 // - runtime/panic.go#gopanic
145 // Panics have their own stacktrace, so no stripping of the current stack
146 skipFrames := 2
147 defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
148 }
149 }
150
151 // OnBeforeNotify adds a callback to be run before a notification is sent to
152 // Bugsnag. It can be used to modify the event or its MetaData. Changes made
153 // to the configuration are local to notifying about this event. To prevent the
154 // event from being sent to Bugsnag return an error, this error will be
155 // returned from bugsnag.Notify() and the event will not be sent.
156 func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
157 middleware.OnBeforeNotify(callback)
158 }
159
160 // Handler creates an http Handler that notifies Bugsnag any panics that
161 // happen. It then repanics so that the default http Server panic handler can
162 // handle the panic too. The rawData is used to send extra information along
163 // with any panics that are handled this way.
164 func Handler(h http.Handler, rawData ...interface{}) http.Handler {
165 notifier := New(rawData...)
166 if h == nil {
167 h = http.DefaultServeMux
168 }
169
170 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
171 request := r
172
173 // Record a session if auto notify session is enabled
174 ctx := r.Context()
175 if Config.IsAutoCaptureSessions() {
176 ctx = StartSession(ctx)
177 }
178 ctx = AttachRequestData(ctx, request)
179 request = r.WithContext(ctx)
180 defer notifier.AutoNotify(ctx, request)
181 h.ServeHTTP(w, request)
182 })
183 }
184
185 // HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
186 // panics that happen. It then repanics so that the default http Server panic
187 // handler can handle the panic too. The rawData is used to send extra
188 // information along with any panics that are handled this way. If you have
189 // already wrapped your http server using bugsnag.Handler() you don't also need
190 // to wrap each HandlerFunc.
191 func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
192 notifier := New(rawData...)
193
194 return func(w http.ResponseWriter, r *http.Request) {
195 request := r
196 // Record a session if auto notify session is enabled
197 ctx := request.Context()
198 if notifier.Config.IsAutoCaptureSessions() {
199 ctx = StartSession(ctx)
200 }
201 ctx = AttachRequestData(ctx, request)
202 request = request.WithContext(ctx)
203 defer notifier.AutoNotify(ctx)
204 h(w, request)
205 }
206 }
207
208 // checkForEmptyError checks if the given error (to be reported to Bugsnag) is
209 // nil. If it is, then log an error message and return another error wrapping
210 // this error message.
211 func checkForEmptyError(err error) error {
212 if err != nil {
213 return nil
214 }
215 msg := "attempted to notify Bugsnag without supplying an error. Bugsnag not notified"
216 Config.Logger.Printf("ERROR: " + msg)
217 return fmt.Errorf(msg)
218 }
219
220 func init() {
221 // Set up builtin middlewarez
222 OnBeforeNotify(httpRequestMiddleware)
223
224 // Default configuration
225 sourceRoot := ""
226 if gopath := os.Getenv("GOPATH"); len(gopath) > 0 {
227 sourceRoot = filepath.Join(gopath, "src") + "/"
228 } else {
229 sourceRoot = filepath.Join(runtime.GOROOT(), "src") + "/"
230 }
231 Config.update(&Configuration{
232 APIKey: "",
233 Endpoints: Endpoints{
234 Notify: "https://notify.bugsnag.com",
235 Sessions: "https://sessions.bugsnag.com",
236 },
237 Hostname: device.GetHostname(),
238 AppType: "",
239 AppVersion: "",
240 AutoCaptureSessions: true,
241 ReleaseStage: "",
242 ParamsFilters: []string{"password", "secret", "authorization", "cookie", "access_token"},
243 SourceRoot: sourceRoot,
244 ProjectPackages: []string{"main*"},
245 NotifyReleaseStages: nil,
246 Logger: log.New(os.Stdout, log.Prefix(), log.Flags()),
247 PanicHandler: defaultPanicHandler,
248 Transport: http.DefaultTransport,
249
250 flushSessionsOnRepanic: true,
251 })
252 updateSessionConfig()
253 }
254
255 func startSessionTracking() {
256 if sessionTracker == nil {
257 updateSessionConfig()
258 sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
259 }
260 }
261
262 func updateSessionConfig() {
263 sessionTrackingConfig.Update(&sessions.SessionTrackingConfiguration{
264 APIKey: Config.APIKey,
265 AutoCaptureSessions: Config.AutoCaptureSessions,
266 Endpoint: Config.Endpoints.Sessions,
267 Version: Version,
268 PublishInterval: DefaultSessionPublishInterval,
269 Transport: Config.Transport,
270 ReleaseStage: Config.ReleaseStage,
271 Hostname: Config.Hostname,
272 AppType: Config.AppType,
273 AppVersion: Config.AppVersion,
274 NotifyReleaseStages: Config.NotifyReleaseStages,
275 Logger: Config.Logger,
276 })
277 }
0 package bugsnag_test
1
2 import (
3 "context"
4 "fmt"
5 "net"
6 "net/http"
7 "time"
8
9 "github.com/bugsnag/bugsnag-go/v2"
10 )
11
12 var exampleAPIKey = "166f5ad3590596f9aa8d601ea89af845"
13
14 func ExampleAutoNotify() {
15 bugsnag.Configure(bugsnag.Configuration{APIKey: exampleAPIKey})
16 createAccount := func(ctx context.Context) {
17 fmt.Println("Creating account and passing context around...")
18 }
19 ctx := bugsnag.StartSession(context.Background())
20 defer bugsnag.AutoNotify(ctx)
21 createAccount(ctx)
22 // Output:
23 // Creating account and passing context around...
24 }
25
26 func ExampleRecover() {
27 bugsnag.Configure(bugsnag.Configuration{APIKey: exampleAPIKey})
28 panicFunc := func() {
29 fmt.Println("About to panic")
30 panic("Oh noes")
31 }
32
33 // Will recover when panicFunc panics
34 func() {
35 ctx := bugsnag.StartSession(context.Background())
36 defer bugsnag.Recover(ctx)
37 panicFunc()
38 }()
39
40 fmt.Println("Panic recovered")
41 // Output: About to panic
42 // Panic recovered
43 }
44
45 func ExampleConfigure() {
46 bugsnag.Configure(bugsnag.Configuration{
47 APIKey: "YOUR_API_KEY_HERE",
48 ReleaseStage: "production",
49 // See bugsnag.Configuration for other fields
50 })
51 }
52
53 func ExampleHandler() {
54 handleReq := func(w http.ResponseWriter, r *http.Request) {
55 fmt.Println("Handling HTTP request")
56 }
57
58 // Set up your http handlers as usual
59 http.HandleFunc("/", handleReq)
60
61 // use bugsnag.Handler(nil) to wrap the default http handlers
62 // so that Bugsnag is automatically notified about panics.
63 http.ListenAndServe(":1234", bugsnag.Handler(nil))
64 }
65
66 func ExampleHandler_customServer() {
67 handleReq := func(w http.ResponseWriter, r *http.Request) {
68 fmt.Println("Handling GET")
69 }
70
71 // If you're using a custom server, set the handlers explicitly.
72 http.HandleFunc("/", handleReq)
73
74 srv := http.Server{
75 Addr: ":1234",
76 ReadTimeout: 10 * time.Second,
77 // use bugsnag.Handler(nil) to wrap the default http handlers
78 // so that Bugsnag is automatically notified about panics.
79 Handler: bugsnag.Handler(nil),
80 }
81 srv.ListenAndServe()
82 }
83
84 func ExampleHandler_customHandlers() {
85 handleReq := func(w http.ResponseWriter, r *http.Request) {
86 fmt.Println("Handling GET")
87 }
88
89 // If you're using custom handlers, wrap the handlers explicitly.
90 handler := http.NewServeMux()
91 http.HandleFunc("/", handleReq)
92 // use bugsnag.Handler(handler) to wrap the handlers so that Bugsnag is
93 // automatically notified about panics
94 http.ListenAndServe(":1234", bugsnag.Handler(handler))
95 }
96
97 func ExampleNotify() {
98 ctx := context.Background()
99 ctx = bugsnag.StartSession(ctx)
100 _, err := net.Listen("tcp", ":80")
101
102 if err != nil {
103 bugsnag.Notify(err, ctx)
104 }
105 }
106
107 func ExampleNotify_details() {
108 ctx := context.Background()
109 ctx = bugsnag.StartSession(ctx)
110 _, err := net.Listen("tcp", ":80")
111
112 if err != nil {
113 bugsnag.Notify(err, ctx,
114 // show as low-severity
115 bugsnag.SeverityInfo,
116 // set the context
117 bugsnag.Context{String: "createlistener"},
118 // pass the user id in to count users affected.
119 bugsnag.User{Id: "123456789"},
120 // custom meta-data tab
121 bugsnag.MetaData{
122 "Listen": {
123 "Protocol": "tcp",
124 "Port": "80",
125 },
126 },
127 )
128 }
129 }
130
131 func ExampleOnBeforeNotify() {
132
133 type Job struct {
134 Retry bool
135 UserID string
136 UserEmail string
137 }
138
139 bugsnag.OnBeforeNotify(func(event *bugsnag.Event, config *bugsnag.Configuration) error {
140 // Search all the RawData for any *Job pointers that we're passed in
141 // to bugsnag.Notify() and friends.
142 for _, datum := range event.RawData {
143 if job, ok := datum.(*Job); ok {
144 // don't notify bugsnag about errors in retries
145 if job.Retry {
146 return fmt.Errorf("bugsnag middleware: not notifying about job retry")
147 }
148 // add the job as a tab on Bugsnag.com
149 event.MetaData.AddStruct("Job", job)
150 // set the user correctly
151 event.User = &bugsnag.User{Id: job.UserID, Email: job.UserEmail}
152 }
153 }
154
155 // continue notifying as normal
156 return nil
157 })
158 }
0 package bugsnag
1
2 import (
3 "context"
4 "fmt"
5 "io/ioutil"
6 "log"
7 "net"
8 "net/http"
9 "net/http/httptest"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/bitly/go-simplejson"
15 "github.com/bugsnag/bugsnag-go/v2/sessions"
16 )
17
18 // The line numbers of this method are used in tests.
19 // If you move this function you'll have to change tests
20 func crashyHandler(w http.ResponseWriter, r *http.Request) {
21 c := make(chan int)
22 close(c)
23 c <- 1
24 }
25
26 type _recurse struct {
27 Recurse *_recurse
28 }
29
30 const (
31 unhandled = true
32 handled = false
33 )
34
35 var testAPIKey = "166f5ad3590596f9aa8d601ea89af845"
36
37 type logger struct{ msg string }
38
39 func (l *logger) Printf(format string, v ...interface{}) { l.msg = format }
40
41 // setup sets up a simple sessionTracker and returns a test event server for receiving the event payloads.
42 // report payloads published to the returned server's URL will be put on the returned channel
43 func setup() (*httptest.Server, chan []byte) {
44 reports := make(chan []byte, 10)
45 sessionTracker = &testSessionTracker{}
46 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
47 body, _ := ioutil.ReadAll(r.Body)
48 reports <- body
49 })), reports
50 }
51
52 type testSessionTracker struct{}
53
54 func (t *testSessionTracker) StartSession(context.Context) context.Context {
55 return context.Background()
56 }
57
58 func (t *testSessionTracker) IncrementEventCountAndGetSession(context.Context, bool) *sessions.Session {
59 return &sessions.Session{}
60 }
61
62 func (t *testSessionTracker) FlushSessions() {}
63
64 func TestConfigure(t *testing.T) {
65 Configure(Configuration{
66 APIKey: testAPIKey,
67 })
68
69 if Config.APIKey != testAPIKey {
70 t.Errorf("Setting APIKey didn't work")
71 }
72
73 if New().Config.APIKey != testAPIKey {
74 t.Errorf("Setting APIKey didn't work for new notifiers")
75 }
76 }
77
78 func TestNotify(t *testing.T) {
79 ts, reports := setup()
80 defer ts.Close()
81 sessionTracker = nil
82 startSessionTracking()
83
84 recurse := _recurse{}
85 recurse.Recurse = &recurse
86
87 OnBeforeNotify(func(event *Event, config *Configuration) error {
88 if event.Context == "testing" {
89 event.GroupingHash = "lol"
90 }
91 return nil
92 })
93
94 md := MetaData{"test": {"password": "sneaky", "value": "able", "broken": complex(1, 2), "recurse": recurse}}
95 user := User{Id: "123", Name: "Conrad", Email: "me@cirw.in"}
96 config := generateSampleConfig(ts.URL)
97 Notify(fmt.Errorf("hello world"), StartSession(context.Background()), config, user, ErrorClass{Name: "ExpectedErrorClass"}, Context{"testing"}, md)
98
99 json, err := simplejson.NewJson(<-reports)
100
101 if err != nil {
102 t.Fatal(err)
103 }
104
105 event := getIndex(json, "events", 0)
106
107 assertPayload(t, json, eventJSON{
108 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
109 Context: "testing",
110 Device: &deviceJSON{Hostname: "web1"},
111 GroupingHash: "lol",
112 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
113 Severity: "warning",
114 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledError},
115 Unhandled: false,
116 Request: &RequestJSON{},
117 User: &User{Id: "123", Name: "Conrad", Email: "me@cirw.in"},
118 Exceptions: []exceptionJSON{{ErrorClass: "ExpectedErrorClass", Message: "hello world"}},
119 })
120 assertValidSession(t, event, handled)
121
122 for k, exp := range map[string]string{
123 "metaData.test.password": "[FILTERED]",
124 "metaData.test.value": "able",
125 "metaData.test.broken": "[complex128]",
126 "metaData.test.recurse.Recurse": "[RECURSION]",
127 } {
128 if got := getString(event, k); got != exp {
129 t.Errorf("Expected %s to be '%s' but was '%s'", k, exp, got)
130 }
131 }
132
133 exception := getIndex(event, "exceptions", 0)
134 verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "TestNotify", LineNumber: 98, InProject: true})
135 }
136
137 type testPublisher struct {
138 sync bool
139 }
140
141 func (tp *testPublisher) publishReport(p *payload) error {
142 tp.sync = p.Synchronous
143 return nil
144 }
145
146 func TestNotifySyncThenAsync(t *testing.T) {
147 ts, _ := setup()
148 defer ts.Close()
149
150 Configure(generateSampleConfig(ts.URL)) //async by default
151
152 pub := new(testPublisher)
153 publisher = pub
154 defer func() { publisher = new(defaultReportPublisher) }()
155
156 Notify(fmt.Errorf("oopsie"))
157 if pub.sync {
158 t.Errorf("Expected notify to be async by default")
159 }
160
161 defaultNotifier.NotifySync(fmt.Errorf("oopsie"), true)
162 if !pub.sync {
163 t.Errorf("Expected notify to be sent synchronously when calling NotifySync with true")
164 }
165
166 Notify(fmt.Errorf("oopsie"))
167 if pub.sync {
168 t.Errorf("Expected notify to be sent asynchronously when calling Notify regardless of previous NotifySync call")
169 }
170 }
171
172 func TestHandlerFunc(t *testing.T) {
173 eventserver, reports := setup()
174 defer eventserver.Close()
175 Configure(generateSampleConfig(eventserver.URL))
176
177 t.Run("unhandled", func(st *testing.T) {
178 sessionTracker = nil
179 startSessionTracking()
180 ts := httptest.NewServer(HandlerFunc(crashyHandler))
181 defer ts.Close()
182
183 http.Get(ts.URL + "/unhandled")
184
185 json, _ := simplejson.NewJson(<-reports)
186 assertPayload(t, json, eventJSON{
187 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
188 Context: "/unhandled",
189 Device: &deviceJSON{Hostname: "web1"},
190 GroupingHash: "",
191 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
192 Severity: "error",
193 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledPanic},
194 Unhandled: true,
195 Request: &RequestJSON{
196 Headers: map[string]string{"Accept-Encoding": "gzip"},
197 HTTPMethod: "GET",
198 URL: ts.URL + "/unhandled",
199 },
200 User: &User{Id: "127.0.0.1", Name: "", Email: ""},
201 Exceptions: []exceptionJSON{{ErrorClass: "runtime.plainError", Message: "send on closed channel"}},
202 })
203 event := getIndex(json, "events", 0)
204 if got, exp := getString(event, "request.headers.Accept-Encoding"), "gzip"; got != exp {
205 st.Errorf("expected Accept-Encoding header to be '%s' but was '%s'", exp, got)
206 }
207 if got, exp := getString(event, "request.httpMethod"), "GET"; got != exp {
208 st.Errorf("expected HTTP method to be '%s' but was '%s'", exp, got)
209 }
210 if got, exp := getString(event, "request.url"), "/unhandled"; !strings.Contains(got, exp) {
211 st.Errorf("expected request URL to contain '%s' but was '%s'", exp, got)
212 }
213 assertValidSession(st, event, unhandled)
214 })
215
216 t.Run("handled", func(st *testing.T) {
217 ts := httptest.NewServer(HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
218 Notify(fmt.Errorf("oopsie"), r.Context())
219 }))
220 defer ts.Close()
221
222 http.Get(ts.URL + "/handled")
223
224 json, _ := simplejson.NewJson(<-reports)
225 assertPayload(t, json, eventJSON{
226 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
227 Context: "/handled",
228 Device: &deviceJSON{Hostname: "web1"},
229 GroupingHash: "",
230 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 1, Unhandled: 0}},
231 Severity: "warning",
232 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledError},
233 Unhandled: false,
234 Request: &RequestJSON{
235 Headers: map[string]string{"Accept-Encoding": "gzip"},
236 HTTPMethod: "GET",
237 URL: ts.URL + "/handled",
238 },
239 User: &User{Id: "127.0.0.1", Name: "", Email: ""},
240 Exceptions: []exceptionJSON{{ErrorClass: "*errors.errorString", Message: "oopsie"}},
241 })
242 event := getIndex(json, "events", 0)
243 if got, exp := getString(event, "request.headers.Accept-Encoding"), "gzip"; got != exp {
244 st.Errorf("expected Accept-Encoding header to be '%s' but was '%s'", exp, got)
245 }
246 if got, exp := getString(event, "request.httpMethod"), "GET"; got != exp {
247 st.Errorf("expected HTTP method to be '%s' but was '%s'", exp, got)
248 }
249 if got, exp := getString(event, "request.url"), "/handled"; !strings.Contains(got, exp) {
250 st.Errorf("expected request URL to contain '%s' but was '%s'", exp, got)
251 }
252 assertValidSession(st, event, handled)
253 })
254 }
255
256 func TestHandler(t *testing.T) {
257 ts, reports := setup()
258 defer ts.Close()
259
260 l, err := net.Listen("tcp", "127.0.0.1:0")
261 if err != nil {
262 t.Fatal(err)
263 }
264 defer l.Close()
265 mux := http.NewServeMux()
266 mux.HandleFunc("/", crashyHandler)
267
268 go (&http.Server{
269 Addr: l.Addr().String(),
270 Handler: Handler(mux, generateSampleConfig(ts.URL), SeverityInfo),
271 ErrorLog: log.New(ioutil.Discard, log.Prefix(), 0),
272 }).Serve(l)
273
274 sessionTracker = nil
275 startSessionTracking()
276
277 http.Get("http://" + l.Addr().String() + "/ok?foo=bar")
278
279 json, err := simplejson.NewJson(<-reports)
280 if err != nil {
281 t.Fatal(err)
282 }
283
284 assertPayload(t, json, eventJSON{
285 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
286 Context: "/ok",
287 Device: &deviceJSON{Hostname: "web1"},
288 GroupingHash: "",
289 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
290 Severity: "info",
291 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledPanic},
292 Unhandled: true,
293 User: &User{Id: "127.0.0.1", Name: "", Email: ""},
294 Request: &RequestJSON{
295 Headers: map[string]string{"Accept-Encoding": "gzip"},
296 HTTPMethod: "GET",
297 URL: "http://" + l.Addr().String() + "/ok?foo=bar",
298 },
299 Exceptions: []exceptionJSON{{ErrorClass: "runtime.plainError", Message: "send on closed channel"}},
300 })
301 event := getIndex(json, "events", 0)
302 if got, exp := getString(event, "request.headers.Accept-Encoding"), "gzip"; got != exp {
303 t.Errorf("expected Accept-Encoding header to be '%s' but was '%s'", exp, got)
304 }
305 if got, exp := getString(event, "request.httpMethod"), "GET"; got != exp {
306 t.Errorf("expected HTTP method to be '%s' but was '%s'", exp, got)
307 }
308 if got, exp := getString(event, "request.url"), "/ok?foo=bar"; !strings.Contains(got, exp) {
309 t.Errorf("expected request URL to be '%s' but was '%s'", exp, got)
310 }
311 assertValidSession(t, event, unhandled)
312 if got, exp := getFirstString(event, "metaData.request.params.foo"), "bar"; got != exp {
313 t.Errorf("Expected metadata params 'foo' to be '%s' but was '%s'", exp, got)
314 }
315
316 exception := getIndex(event, "exceptions", 0)
317 verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "crashyHandler", InProject: true, LineNumber: 24})
318 }
319
320 func TestAutoNotify(t *testing.T) {
321 ts, reports := setup()
322 defer ts.Close()
323
324 var panicked error
325
326 func() {
327 defer func() {
328 p := recover()
329 switch p.(type) {
330 case error:
331 panicked = p.(error)
332 default:
333 t.Fatalf("Unexpected panic happened. Expected 'eggs' Error but was a(n) <%T> with value <%+v>", p, p)
334 }
335 }()
336 defer AutoNotify(StartSession(context.Background()), generateSampleConfig(ts.URL))
337
338 panic(fmt.Errorf("eggs"))
339 }()
340
341 if panicked.Error() != "eggs" {
342 t.Errorf("didn't re-panic")
343 }
344
345 json, err := simplejson.NewJson(<-reports)
346 if err != nil {
347 t.Fatal(err)
348 }
349
350 assertPayload(t, json, eventJSON{
351 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
352 Context: "",
353 Device: &deviceJSON{Hostname: "web1"},
354 GroupingHash: "",
355 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
356 Severity: "error",
357 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledPanic},
358 Unhandled: true,
359 User: &User{},
360 Request: &RequestJSON{},
361 Exceptions: []exceptionJSON{{ErrorClass: "*errors.errorString", Message: "eggs"}},
362 })
363 }
364
365 func TestRecover(t *testing.T) {
366 ts, reports := setup()
367 defer ts.Close()
368
369 var panicked interface{}
370
371 func() {
372 defer func() {
373 panicked = recover()
374 }()
375 defer Recover(StartSession(context.Background()), generateSampleConfig(ts.URL))
376
377 panic("ham")
378 }()
379
380 if panicked != nil {
381 t.Errorf("Did not expect a panic but repanicked")
382 }
383
384 json, err := simplejson.NewJson(<-reports)
385 if err != nil {
386 t.Fatal(err)
387 }
388
389 assertPayload(t, json, eventJSON{
390 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
391 Context: "",
392 Device: &deviceJSON{Hostname: "web1"},
393 GroupingHash: "",
394 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
395 Severity: "warning",
396 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledPanic},
397 Unhandled: false,
398 Request: &RequestJSON{},
399 User: &User{},
400 Exceptions: []exceptionJSON{{ErrorClass: "*errors.errorString", Message: "ham"}},
401 })
402 }
403
404 func TestRecoverCustomHandledState(t *testing.T) {
405 ts, reports := setup()
406 defer ts.Close()
407
408 var panicked interface{}
409
410 func() {
411 defer func() {
412 panicked = recover()
413 }()
414 handledState := HandledState{
415 SeverityReason: SeverityReasonHandledPanic,
416 OriginalSeverity: SeverityError,
417 Unhandled: true,
418 }
419 defer Recover(handledState, StartSession(context.Background()), generateSampleConfig(ts.URL))
420
421 panic("at the disco?")
422 }()
423
424 if panicked != nil {
425 t.Errorf("Did not expect a panic but repanicked")
426 }
427 json, err := simplejson.NewJson(<-reports)
428 if err != nil {
429 t.Fatal(err)
430 }
431
432 assertPayload(t, json, eventJSON{
433 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
434 Context: "",
435 Device: &deviceJSON{Hostname: "web1"},
436 GroupingHash: "",
437 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
438 Severity: "error",
439 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledPanic},
440 Unhandled: true,
441 Request: &RequestJSON{},
442 User: &User{},
443 Exceptions: []exceptionJSON{{ErrorClass: "*errors.errorString", Message: "at the disco?"}},
444 })
445 }
446
447 func TestSeverityReasonNotifyCallback(t *testing.T) {
448 ts, reports := setup()
449 defer ts.Close()
450
451 OnBeforeNotify(func(event *Event, config *Configuration) error {
452 event.Severity = SeverityInfo
453 return nil
454 })
455
456 Notify(fmt.Errorf("hello world"), generateSampleConfig(ts.URL), StartSession(context.Background()))
457
458 json, _ := simplejson.NewJson(<-reports)
459 assertPayload(t, json, eventJSON{
460 App: &appJSON{ReleaseStage: "test", Type: "foo", Version: "1.2.3"},
461 Context: "",
462 Device: &deviceJSON{Hostname: "web1"},
463 GroupingHash: "",
464 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
465 Severity: "info",
466 SeverityReason: &severityReasonJSON{Type: SeverityReasonCallbackSpecified},
467 Unhandled: false,
468 Request: &RequestJSON{},
469 User: &User{},
470 Exceptions: []exceptionJSON{{ErrorClass: "*errors.errorString", Message: "hello world"}},
471 })
472 }
473
474 func TestNotifyWithoutError(t *testing.T) {
475 ts, reports := setup()
476 defer ts.Close()
477
478 config := generateSampleConfig(ts.URL)
479 config.Synchronous = true
480 l := logger{}
481 config.Logger = &l
482 Configure(config)
483
484 Notify(nil, StartSession(context.Background()))
485
486 select {
487 case r := <-reports:
488 t.Fatalf("Unexpected request made to bugsnag: %+v", string(r))
489 default:
490 for _, exp := range []string{"ERROR", "error", "Bugsnag", "not notified"} {
491 if got := l.msg; !strings.Contains(got, exp) {
492 t.Errorf("Expected to see '%s' in logged message but logged message was '%s'", exp, got)
493 }
494 }
495 }
496 }
497
498 func TestConfigureTwice(t *testing.T) {
499 Configure(Configuration{})
500 if !Config.IsAutoCaptureSessions() {
501 t.Errorf("Expected auto capture sessions to be enabled by default")
502 }
503 Configure(Configuration{AutoCaptureSessions: false})
504 if Config.IsAutoCaptureSessions() {
505 t.Errorf("Expected auto capture sessions to be disabled when configured")
506 }
507 Configure(Configuration{AutoCaptureSessions: true})
508 if !Config.IsAutoCaptureSessions() {
509 t.Errorf("Expected auto capture sessions to be enabled when configured")
510 }
511 }
512
513 func generateSampleConfig(endpoint string) Configuration {
514 return Configuration{
515 APIKey: testAPIKey,
516 Endpoints: Endpoints{Notify: endpoint},
517 ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"},
518 Logger: log.New(ioutil.Discard, log.Prefix(), log.Flags()),
519 ReleaseStage: "test",
520 AppType: "foo",
521 AppVersion: "1.2.3",
522 Hostname: "web1",
523 }
524 }
525
526 func get(j *simplejson.Json, path string) *simplejson.Json {
527 return j.GetPath(strings.Split(path, ".")...)
528 }
529 func getBool(j *simplejson.Json, path string) bool {
530 return get(j, path).MustBool()
531 }
532 func getInt(j *simplejson.Json, path string) int {
533 return get(j, path).MustInt()
534 }
535 func getString(j *simplejson.Json, path string) string {
536 return get(j, path).MustString()
537 }
538 func getIndex(j *simplejson.Json, path string, index int) *simplejson.Json {
539 return get(j, path).GetIndex(index)
540 }
541 func getFirstString(j *simplejson.Json, path string) string {
542 return getIndex(j, path, 0).MustString()
543 }
544
545 // assertPayload compares the payload that was received by the event-server to
546 // the expected report JSON payload
547 func assertPayload(t *testing.T, report *simplejson.Json, exp eventJSON) {
548 expException := exp.Exceptions[0]
549
550 event := getIndex(report, "events", 0)
551 exception := getIndex(event, "exceptions", 0)
552
553 for _, tc := range []struct {
554 prop string
555 exp, got interface{}
556 }{
557 {prop: "API Key", exp: testAPIKey, got: getString(report, "apiKey")},
558
559 {prop: "notifier name", exp: "Bugsnag Go", got: getString(report, "notifier.name")},
560 {prop: "notifier version", exp: Version, got: getString(report, "notifier.version")},
561 {prop: "notifier url", exp: "https://github.com/bugsnag/bugsnag-go", got: getString(report, "notifier.url")},
562
563 {prop: "exception message", exp: expException.Message, got: getString(exception, "message")},
564 {prop: "exception error class", exp: expException.ErrorClass, got: getString(exception, "errorClass")},
565
566 {prop: "unhandled", exp: exp.Unhandled, got: getBool(event, "unhandled")},
567
568 {prop: "app version", exp: exp.App.Version, got: getString(event, "app.version")},
569 {prop: "app release stage", exp: exp.App.ReleaseStage, got: getString(event, "app.releaseStage")},
570 {prop: "app type", exp: exp.App.Type, got: getString(event, "app.type")},
571
572 {prop: "user id", exp: exp.User.Id, got: getString(event, "user.id")},
573 {prop: "user name", exp: exp.User.Name, got: getString(event, "user.name")},
574 {prop: "user email", exp: exp.User.Email, got: getString(event, "user.email")},
575
576 {prop: "context", exp: exp.Context, got: getString(event, "context")},
577 {prop: "device hostname", exp: exp.Device.Hostname, got: getString(event, "device.hostname")},
578 {prop: "grouping hash", exp: exp.GroupingHash, got: getString(event, "groupingHash")},
579 {prop: "payload version", exp: "4", got: getString(event, "payloadVersion")},
580
581 {prop: "severity", exp: exp.Severity, got: getString(event, "severity")},
582 {prop: "severity reason type", exp: string(exp.SeverityReason.Type), got: getString(event, "severityReason.type")},
583
584 {prop: "request header 'Accept-Encoding'", exp: string(exp.Request.Headers["Accept-Encoding"]), got: getString(event, "request.headers.Accept-Encoding")},
585 {prop: "request HTTP method", exp: string(exp.Request.HTTPMethod), got: getString(event, "request.httpMethod")},
586 {prop: "request URL", exp: string(exp.Request.URL), got: getString(event, "request.url")},
587 } {
588 if tc.got != tc.exp {
589 t.Errorf("Wrong %s: expected '%v' but got '%v'", tc.prop, tc.exp, tc.got)
590 }
591 }
592 }
593
594 func assertValidSession(t *testing.T, event *simplejson.Json, unhandled bool) {
595 if sessionID := getString(event, "session.id"); len(sessionID) != 36 {
596 t.Errorf("Expected a valid session ID to be set but was '%s'", sessionID)
597 }
598 if _, e := time.Parse(time.RFC3339, getString(event, "session.startedAt")); e != nil {
599 t.Error(e)
600 }
601 expHandled, expUnhandled := 1, 0
602 if unhandled {
603 expHandled, expUnhandled = expUnhandled, expHandled
604 }
605 if got := getInt(event, "session.events.unhandled"); got != expUnhandled {
606 t.Errorf("Expected %d unhandled events in session but was %d", expUnhandled, got)
607 }
608 if got := getInt(event, "session.events.handled"); got != expHandled {
609 t.Errorf("Expected %d handled events in session but was %d", expHandled, got)
610 }
611 }
612
613 func verifyExistsInStackTrace(t *testing.T, exception *simplejson.Json, exp *StackFrame) {
614 isFile := func(frame *simplejson.Json) bool { return strings.HasSuffix(getString(frame, "file"), exp.File) }
615 isMethod := func(frame *simplejson.Json) bool { return getString(frame, "method") == exp.Method }
616 isLineNumber := func(frame *simplejson.Json) bool { return getInt(frame, "lineNumber") == exp.LineNumber }
617
618 arr, _ := exception.Get("stacktrace").Array()
619 for i := 0; i < len(arr); i++ {
620 frame := getIndex(exception, "stacktrace", i)
621 if isFile(frame) && isMethod(frame) && isLineNumber(frame) {
622 return
623 }
624 }
625 t.Errorf("Could not find expected stackframe %v in exception '%v'", exp, exception)
626 }
0 package bugsnag
1
2 import (
3 "log"
4 "net/http"
5 "os"
6 "path/filepath"
7 "runtime"
8 "strings"
9 )
10
11 // Endpoints hold the HTTP endpoints of the notifier.
12 type Endpoints struct {
13 Sessions string
14 Notify string
15 }
16
17 // Configuration sets up and customizes communication with the Bugsnag API.
18 type Configuration struct {
19 // Your Bugsnag API key, e.g. "c9d60ae4c7e70c4b6c4ebd3e8056d2b8". You can
20 // find this by clicking Settings on https://bugsnag.com/.
21 APIKey string
22
23 // Endpoints define the HTTP endpoints that the notifier should notify
24 // about crashes and sessions. These default to notify.bugsnag.com for
25 // error reports and sessions.bugsnag.com for sessions.
26 // If you are using bugsnag on-premise you will have to set these to your
27 // Event Server and Session Server endpoints. If the notify endpoint is set
28 // but the sessions endpoint is not, session tracking will be disabled
29 // automatically to avoid leaking session information outside of your
30 // server configuration, and a warning will be logged.
31 Endpoints Endpoints
32
33 // The current release stage. This defaults to "production" and is used to
34 // filter errors in the Bugsnag dashboard.
35 ReleaseStage string
36 // A specialized type of the application, such as the worker queue or web
37 // framework used, like "rails", "mailman", or "celery"
38 AppType string
39 // The currently running version of the app. This is used to filter errors
40 // in the Bugsnag dasboard. If you set this then Bugsnag will only re-open
41 // resolved errors if they happen in different app versions.
42 AppVersion string
43
44 // AutoCaptureSessions can be set to false to disable automatic session
45 // tracking. If you want control over what is deemed a session, you can
46 // switch off automatic session tracking with this configuration, and call
47 // bugsnag.StartSession() when appropriate for your application. See the
48 // official docs for instructions and examples of associating handled
49 // errors with sessions and ensuring error rate accuracy on the Bugsnag
50 // dashboard. This will default to true, but is stored as an interface to enable
51 // us to detect when this option has not been set.
52 AutoCaptureSessions interface{}
53
54 // The hostname of the current server. This defaults to the return value of
55 // os.Hostname() and is graphed in the Bugsnag dashboard.
56 Hostname string
57
58 // The Release stages to notify in. If you set this then bugsnag-go will
59 // only send notifications to Bugsnag if the ReleaseStage is listed here.
60 NotifyReleaseStages []string
61
62 // packages that are part of your app. Bugsnag uses this to determine how
63 // to group errors and how to display them on your dashboard. You should
64 // include any packages that are part of your app, and exclude libraries
65 // and helpers. You can list wildcards here, and they'll be expanded using
66 // filepath.Glob. The default value is []string{"main*"}
67 ProjectPackages []string
68
69 // The SourceRoot is the directory where the application is built, and the
70 // assumed prefix of lines on the stacktrace originating in the parent
71 // application. When set, the prefix is trimmed from callstack file names
72 // before ProjectPackages for better readability and to better group errors
73 // on the Bugsnag dashboard. The default value is $GOPATH/src or $GOROOT/src
74 // if $GOPATH is unset. At runtime, $GOROOT is the root used during the Go
75 // build.
76 SourceRoot string
77
78 // Any meta-data that matches these filters will be marked as [FILTERED]
79 // before sending a Notification to Bugsnag. It defaults to
80 // []string{"password", "secret"} so that request parameters like password,
81 // password_confirmation and auth_secret will not be sent to Bugsnag.
82 ParamsFilters []string
83
84 // The PanicHandler is used by Bugsnag to catch unhandled panics in your
85 // application. The default panicHandler uses mitchellh's panicwrap library,
86 // and you can disable this feature by passing an empty: func() {}
87 PanicHandler func()
88
89 // The logger that Bugsnag should log to. Uses the same defaults as go's
90 // builtin logging package. bugsnag-go logs whenever it notifies Bugsnag
91 // of an error, and when any error occurs inside the library itself.
92 Logger interface {
93 Printf(format string, v ...interface{}) // limited to the functions used
94 }
95 // The http Transport to use, defaults to the default http Transport. This
96 // can be configured if you are in an environment
97 // that has stringent conditions on making http requests.
98 Transport http.RoundTripper
99 // Whether bugsnag should notify synchronously. This defaults to false which
100 // causes bugsnag-go to spawn a new goroutine for each notification.
101 Synchronous bool
102 // Whether the notifier should send all sessions recorded so far to Bugsnag
103 // when repanicking to ensure that no session information is lost in a
104 // fatal crash.
105 flushSessionsOnRepanic bool
106 // TODO: remember to update the update() function when modifying this struct
107 }
108
109 func (config *Configuration) update(other *Configuration) *Configuration {
110 if other.APIKey != "" {
111 config.APIKey = other.APIKey
112 }
113 if other.Hostname != "" {
114 config.Hostname = other.Hostname
115 }
116 if other.AppType != "" {
117 config.AppType = other.AppType
118 }
119 if other.AppVersion != "" {
120 config.AppVersion = other.AppVersion
121 }
122 if other.SourceRoot != "" {
123 config.SourceRoot = other.SourceRoot
124 // Use '/' as the separator as Go stacktraces are printed with '/' as
125 // the separator regardless of os.PathSeparator.
126 if runtime.GOOS == "windows" {
127 config.SourceRoot = strings.Replace(config.SourceRoot, "\\", "/", -1)
128 }
129 }
130 if other.ReleaseStage != "" {
131 config.ReleaseStage = other.ReleaseStage
132 }
133 if other.ParamsFilters != nil {
134 config.ParamsFilters = other.ParamsFilters
135 }
136 if other.ProjectPackages != nil {
137 config.ProjectPackages = other.ProjectPackages
138 // Use '/' as the separator as Go stacktraces are printed with '/' as
139 // the separator regardless of os.PathSeparator.
140 if runtime.GOOS == "windows" {
141 for idx, pkg := range config.ProjectPackages {
142 config.ProjectPackages[idx] = strings.Replace(pkg, "\\", "/", -1)
143 }
144 }
145 }
146 if other.Logger != nil {
147 config.Logger = other.Logger
148 }
149 if other.NotifyReleaseStages != nil {
150 config.NotifyReleaseStages = other.NotifyReleaseStages
151 }
152 if other.PanicHandler != nil {
153 config.PanicHandler = other.PanicHandler
154 }
155 if other.Transport != nil {
156 config.Transport = other.Transport
157 }
158 if other.Synchronous {
159 config.Synchronous = true
160 }
161
162 if other.AutoCaptureSessions != nil {
163 config.AutoCaptureSessions = other.AutoCaptureSessions
164 }
165 config.updateEndpoints(&other.Endpoints)
166 return config
167 }
168
169 // IsAutoCaptureSessions identifies whether or not the notifier should
170 // automatically capture sessions as requests come in. It's a convenience
171 // wrapper that allows automatic session capturing to be enabled by default.
172 func (config *Configuration) IsAutoCaptureSessions() bool {
173 if config.AutoCaptureSessions == nil {
174 return true // enabled by default
175 }
176 if val, ok := config.AutoCaptureSessions.(bool); ok {
177 return val
178 }
179 // It has been configured to *something* (although not a valid value)
180 // assume the user wanted to disable this option.
181 return false
182 }
183
184 func (config *Configuration) updateEndpoints(endpoints *Endpoints) {
185 if endpoints.Notify != "" {
186 config.Endpoints.Notify = endpoints.Notify
187 if endpoints.Sessions == "" {
188 config.Logger.Printf("WARNING: Bugsnag notify endpoint configured without also configuring the sessions endpoint. No sessions will be recorded")
189 config.Endpoints.Sessions = ""
190 }
191 }
192 if endpoints.Sessions != "" {
193 if endpoints.Notify == "" {
194 panic("FATAL: Bugsnag sessions endpoint configured without also changing the notify endpoint. Bugsnag cannot identify where to report errors")
195 }
196 config.Endpoints.Sessions = endpoints.Sessions
197 }
198 }
199
200 func (config *Configuration) merge(other *Configuration) *Configuration {
201 return config.clone().update(other)
202 }
203
204 func (config *Configuration) clone() *Configuration {
205 clone := *config
206 return &clone
207 }
208
209 func (config *Configuration) isProjectPackage(_pkg string) bool {
210 sep := string(filepath.Separator)
211 // filepath functions only work if the contents of the paths use the system
212 // file separator
213 format := func(s string) string {
214 return strings.Replace(s, "/", sep, -1)
215 }
216 pkg := format(_pkg)
217 for _, _p := range config.ProjectPackages {
218 p := format(_p)
219 if d, f := filepath.Split(p); f == "**" {
220 if strings.HasPrefix(pkg, d) {
221 return true
222 }
223 }
224
225 if match, _ := filepath.Match(p, pkg); match {
226 return true
227 }
228 }
229 return false
230 }
231
232 func (config *Configuration) stripProjectPackages(file string) string {
233 trimmedFile := file
234 if strings.HasPrefix(trimmedFile, config.SourceRoot) {
235 trimmedFile = strings.TrimPrefix(trimmedFile, config.SourceRoot)
236 }
237 for _, p := range config.ProjectPackages {
238 if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
239 p = p[:len(p)-1]
240 } else if p[len(p)-1] == '*' && p[len(p)-2] == '*' {
241 p = p[:len(p)-2]
242 } else {
243 p = p + "/"
244 }
245 if strings.HasPrefix(trimmedFile, p) {
246 return strings.TrimPrefix(trimmedFile, p)
247 }
248 }
249
250 return trimmedFile
251 }
252
253 func (config *Configuration) logf(fmt string, args ...interface{}) {
254 if config != nil && config.Logger != nil {
255 config.Logger.Printf(fmt, args...)
256 } else {
257 log.Printf(fmt, args...)
258 }
259 }
260
261 func (config *Configuration) notifyInReleaseStage() bool {
262 if config.NotifyReleaseStages == nil {
263 return true
264 }
265 if config.ReleaseStage == "" {
266 return true
267 }
268 for _, r := range config.NotifyReleaseStages {
269 if r == config.ReleaseStage {
270 return true
271 }
272 }
273 return false
274 }
275
276 func (config *Configuration) loadEnv() {
277 envConfig := Configuration{}
278 if apiKey := os.Getenv("BUGSNAG_API_KEY"); apiKey != "" {
279 envConfig.APIKey = apiKey
280 }
281 if endpoint := os.Getenv("BUGSNAG_SESSIONS_ENDPOINT"); endpoint != "" {
282 envConfig.Endpoints.Sessions = endpoint
283 }
284 if endpoint := os.Getenv("BUGSNAG_NOTIFY_ENDPOINT"); endpoint != "" {
285 envConfig.Endpoints.Notify = endpoint
286 }
287 if stage := os.Getenv("BUGSNAG_RELEASE_STAGE"); stage != "" {
288 envConfig.ReleaseStage = stage
289 }
290 if appVersion := os.Getenv("BUGSNAG_APP_VERSION"); appVersion != "" {
291 envConfig.AppVersion = appVersion
292 }
293 if hostname := os.Getenv("BUGSNAG_HOSTNAME"); hostname != "" {
294 envConfig.Hostname = hostname
295 }
296 if sourceRoot := os.Getenv("BUGSNAG_SOURCE_ROOT"); sourceRoot != "" {
297 envConfig.SourceRoot = sourceRoot
298 }
299 if appType := os.Getenv("BUGSNAG_APP_TYPE"); appType != "" {
300 envConfig.AppType = appType
301 }
302 if stages := os.Getenv("BUGSNAG_NOTIFY_RELEASE_STAGES"); stages != "" {
303 envConfig.NotifyReleaseStages = strings.Split(stages, ",")
304 }
305 if packages := os.Getenv("BUGSNAG_PROJECT_PACKAGES"); packages != "" {
306 envConfig.ProjectPackages = strings.Split(packages, ",")
307 }
308 if synchronous := os.Getenv("BUGSNAG_SYNCHRONOUS"); synchronous != "" {
309 envConfig.Synchronous = synchronous == "1"
310 }
311 if disablePanics := os.Getenv("BUGSNAG_DISABLE_PANIC_HANDLER"); disablePanics == "1" {
312 envConfig.PanicHandler = func() {}
313 }
314 if autoSessions := os.Getenv("BUGSNAG_AUTO_CAPTURE_SESSIONS"); autoSessions != "" {
315 envConfig.AutoCaptureSessions = autoSessions == "1"
316 }
317 if filters := os.Getenv("BUGSNAG_PARAMS_FILTERS"); filters != "" {
318 envConfig.ParamsFilters = strings.Split(filters, ",")
319 }
320
321 metadata := loadEnvMetadata(os.Environ())
322 OnBeforeNotify(func(event *Event, config *Configuration) error {
323 for _, m := range metadata {
324 event.MetaData.Add(m.tab, m.key, m.value)
325 }
326
327 return nil
328 })
329
330 config.update(&envConfig)
331 }
0 package bugsnag
1
2 import (
3 "log"
4 "os"
5 "runtime"
6 "strings"
7 "testing"
8 )
9
10 func TestNotifyReleaseStages(t *testing.T) {
11
12 notify := " "
13
14 var tt = []struct {
15 releaseStage string
16 notifyReleaseStages []string
17 expected bool
18 }{
19 {
20 releaseStage: "production",
21 expected: true,
22 },
23 {
24 releaseStage: "production",
25 notifyReleaseStages: []string{"development", "production"},
26 expected: true,
27 },
28 {
29 releaseStage: "staging",
30 notifyReleaseStages: []string{"development", "production"},
31 expected: false,
32 },
33 {
34 notifyReleaseStages: []string{"development", "production"},
35 expected: true,
36 },
37 }
38
39 for _, tc := range tt {
40 rs, nrs, exp := tc.releaseStage, tc.notifyReleaseStages, tc.expected
41 config := &Configuration{ReleaseStage: rs, NotifyReleaseStages: nrs}
42 if config.notifyInReleaseStage() != exp {
43 if !exp {
44 notify = " not "
45 }
46 t.Errorf("expected%sto notify when release stage is '%s' and notify release stages are '%+v'", notify, rs, nrs)
47 }
48 }
49 }
50
51 func TestIsProjectPackage(t *testing.T) {
52
53 Configure(Configuration{ProjectPackages: []string{
54 "main",
55 "star*",
56 "example.com/a",
57 "example.com/b/*",
58 "example.com/c/*/*",
59 "example.com/d/**",
60 "example.com/e",
61 }})
62
63 var testCases = []struct {
64 Path string
65 Included bool
66 }{
67 {"", false},
68 {"main", true},
69 {"runtime", false},
70
71 {"star", true},
72 {"sta", false},
73 {"starred", true},
74 {"star/foo", false},
75
76 {"example.com/a", true},
77
78 {"example.com/b", false},
79 {"example.com/b/", true},
80 {"example.com/b/foo", true},
81 {"example.com/b/foo/bar", false},
82
83 {"example.com/c/foo/bar", true},
84 {"example.com/c/foo/bar/baz", false},
85
86 {"example.com/d/foo/bar", true},
87 {"example.com/d/foo/bar/baz", true},
88
89 {"example.com/e", true},
90 }
91
92 for _, s := range testCases {
93 if Config.isProjectPackage(s.Path) != s.Included {
94 t.Error("literal project package doesn't work:", s.Path, s.Included)
95 }
96 }
97 }
98
99 func TestStripProjectPackage(t *testing.T) {
100 gopath := os.Getenv("GOPATH")
101 Configure(Configuration{
102 ProjectPackages: []string{
103 "main",
104 "star*",
105 "example.com/a",
106 "example.com/b/*",
107 "example.com/c/**",
108 },
109 SourceRoot: gopath + "/src/",
110 })
111
112 // on windows, source lines always use '/' but GOPATH may use '\' depending
113 // on user settings.
114 adjustedGopath := strings.Replace(gopath, "\\", "/", -1)
115 var testCases = []struct {
116 File string
117 Stripped string
118 }{
119 {"main.go", "main.go"},
120 {"runtime.go", "runtime.go"},
121 {"star.go", "star.go"},
122
123 {"example.com/a/foo.go", "foo.go"},
124
125 {"example.com/b/foo/bar.go", "foo/bar.go"},
126 {"example.com/b/foo.go", "foo.go"},
127
128 {"example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
129
130 {"example.com/c/a/b/foo.go", "a/b/foo.go"},
131
132 {adjustedGopath + "/src/runtime.go", "runtime.go"},
133 {adjustedGopath + "/src/example.com/a/foo.go", "foo.go"},
134 {adjustedGopath + "/src/example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
135 {adjustedGopath + "/src/example.com/c/a/b/foo.go", "a/b/foo.go"},
136 }
137
138 for _, tc := range testCases {
139 if s := Config.stripProjectPackages(tc.File); s != tc.Stripped {
140 t.Error("stripProjectPackage did not remove expected path:", tc.File, tc.Stripped, "was:", s)
141 }
142 }
143 }
144
145 func TestStripCustomSourceRoot(t *testing.T) {
146 Configure(Configuration{
147 ProjectPackages: []string{
148 "main",
149 "star*",
150 "example.com/a",
151 "example.com/b/*",
152 "example.com/c/**",
153 },
154 SourceRoot: "/Users/bob/code/go/src/",
155 })
156 var testCases = []struct {
157 File string
158 Stripped string
159 }{
160 {"main.go", "main.go"},
161 {"runtime.go", "runtime.go"},
162 {"star.go", "star.go"},
163
164 {"example.com/a/foo.go", "foo.go"},
165
166 {"example.com/b/foo/bar.go", "foo/bar.go"},
167 {"example.com/b/foo.go", "foo.go"},
168
169 {"example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
170
171 {"example.com/c/a/b/foo.go", "a/b/foo.go"},
172
173 {"/Users/bob/code/go/src/runtime.go", "runtime.go"},
174 {"/Users/bob/code/go/src/example.com/a/foo.go", "foo.go"},
175 {"/Users/bob/code/go/src/example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
176 {"/Users/bob/code/go/src/example.com/c/a/b/foo.go", "a/b/foo.go"},
177 }
178
179 for _, tc := range testCases {
180 if s := Config.stripProjectPackages(tc.File); s != tc.Stripped {
181 t.Error("stripProjectPackage did not remove expected path:", tc.File, tc.Stripped, "was:", s)
182 }
183 }
184 }
185
186 func TestStripCustomWindowsSourceRoot(t *testing.T) {
187 if runtime.GOOS != "windows" {
188 t.Skip("not compatible with non-windows builds")
189 return
190 }
191 Configure(Configuration{
192 ProjectPackages: []string{
193 "main",
194 "star*",
195 "example.com/a",
196 "example.com\\b\\*",
197 "example.com/c/**",
198 },
199 SourceRoot: "C:\\Users\\bob\\code\\go\\src\\",
200 })
201 var testCases = []struct {
202 File string
203 Stripped string
204 }{
205 {"main.go", "main.go"},
206 {"runtime.go", "runtime.go"},
207 {"star.go", "star.go"},
208
209 {"example.com/a/foo.go", "foo.go"},
210
211 {"example.com/b/foo/bar.go", "foo/bar.go"},
212 {"example.com/b/foo.go", "foo.go"},
213
214 {"example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
215
216 {"example.com/c/a/b/foo.go", "a/b/foo.go"},
217
218 {"C:/Users/bob/code/go/src/runtime.go", "runtime.go"},
219 {"C:/Users/bob/code/go/src/example.com/a/foo.go", "foo.go"},
220 {"C:/Users/bob/code/go/src/example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
221 {"C:/Users/bob/code/go/src/example.com/c/a/b/foo.go", "a/b/foo.go"},
222 }
223
224 for _, tc := range testCases {
225 if s := Config.stripProjectPackages(tc.File); s != tc.Stripped {
226 t.Error("stripProjectPackage did not remove expected path:", tc.File, tc.Stripped, "was:", s)
227 }
228 }
229 }
230
231 type CustomTestLogger struct {
232 loggedMessages []string
233 }
234
235 func (logger *CustomTestLogger) Printf(format string, v ...interface{}) {
236 logger.loggedMessages = append(logger.loggedMessages, format)
237 }
238
239 func TestConfiguringCustomLogger(t *testing.T) {
240
241 l1 := log.New(os.Stdout, "", log.Lshortfile)
242
243 l2 := &CustomTestLogger{}
244
245 var testCases = []struct {
246 config Configuration
247 notify bool
248 msg string
249 }{
250 {
251 config: Configuration{ReleaseStage: "production", NotifyReleaseStages: []string{"development", "production"}, Logger: l1},
252 },
253 {
254 config: Configuration{ReleaseStage: "production", NotifyReleaseStages: []string{"development", "production"}, Logger: l2},
255 },
256 }
257
258 for _, testCase := range testCases {
259 Configure(testCase.config)
260
261 // call printf just to illustrate it is present as the compiler does most of the hard work
262 testCase.config.Logger.Printf("hello %s", "bugsnag")
263
264 }
265 }
266
267 func TestEndpointDeprecationWarning(t *testing.T) {
268 defaultNotify := "https://notify.bugsnag.com/"
269 defaultSessions := "https://sessions.bugsnag.com/"
270 setUp := func() (*Configuration, *CustomTestLogger) {
271 logger := &CustomTestLogger{}
272 return &Configuration{
273 Endpoints: Endpoints{
274 Notify: defaultNotify,
275 Sessions: defaultSessions,
276 },
277 Logger: logger,
278 }, logger
279 }
280
281 t.Run("Setting Endpoints.Notify without setting Endpoints.Sessions gives session disabled warning", func(st *testing.T) {
282 c, logger := setUp()
283 config := Configuration{
284 Endpoints: Endpoints{
285 Notify: "https://notify.whatever.com/",
286 },
287 }
288 keywords := []string{"WARNING", "Bugsnag", "notify", "No sessions"}
289 c.update(&config)
290 if got := len(logger.loggedMessages); got != 1 {
291 st.Errorf("Expected exactly one logged message but got %d", got)
292 }
293 got := logger.loggedMessages[0]
294 for _, exp := range keywords {
295 if !strings.Contains(got, exp) {
296 st.Errorf("Expected logger message containing '%s' when configuring but got %s.", exp, got)
297 }
298 }
299 if got, exp := c.Endpoints.Notify, config.Endpoints.Notify; got != exp {
300 st.Errorf("Expected notify endpoint to be '%s' but was '%s'", exp, got)
301 }
302 if got, exp := c.Endpoints.Sessions, ""; got != exp {
303 st.Errorf("Expected sessions endpoint to be '%s' but was '%s'", exp, got)
304 }
305 })
306
307 t.Run("Setting Endpoints.Sessions without setting Endpoints.Notify should panic", func(st *testing.T) {
308 c, _ := setUp()
309 defer func() {
310 if err := recover(); err != nil {
311 got := err.(string)
312 for _, exp := range []string{"FATAL", "Bugsnag", "notify", "sessions"} {
313 if !strings.Contains(got, exp) {
314 st.Errorf("Expected panic error containing '%s' when configuring but got %s.", exp, got)
315 }
316 }
317 } else {
318 st.Errorf("Expected a panic to happen but didn't")
319 }
320 }()
321 c.update(&Configuration{
322 Endpoints: Endpoints{
323 Sessions: "https://sessions.whatever.com/",
324 },
325 })
326 })
327
328 t.Run("Should not complain if both Endpoints.Notify and Endpoints.Sessions are configured", func(st *testing.T) {
329 notifyEndpoint, sessionsEndpoint := "https://notify.whatever.com", "https://sessions.whatever.com"
330 config := Configuration{
331 Endpoints: Endpoints{
332 Notify: notifyEndpoint,
333 Sessions: sessionsEndpoint,
334 },
335 }
336 c, logger := setUp()
337 c.update(&config)
338 if len(logger.loggedMessages) != 0 {
339 st.Errorf("Did not expect any messages to be logged but logged: %v", logger.loggedMessages)
340 }
341 if got, exp := c.Endpoints.Notify, notifyEndpoint; got != exp {
342 st.Errorf("Expected Notify endpoint: '%s', but was: '%s'", exp, got)
343 }
344 if got, exp := c.Endpoints.Sessions, sessionsEndpoint; got != exp {
345 st.Errorf("Expected Sessions endpoint: '%s', but was: '%s'", exp, got)
346 }
347 })
348
349 t.Run("Should not complain if Endpoints are not configured", func(st *testing.T) {
350 c, logger := setUp()
351 c.update(&Configuration{})
352 if len(logger.loggedMessages) != 0 {
353 st.Errorf("Did not expect any messages to be logged but logged: %v", logger.loggedMessages)
354 }
355 if got, exp := c.Endpoints.Notify, defaultNotify; got != exp {
356 st.Errorf("Expected Notify endpoint: '%s', but was: '%s'", exp, got)
357 }
358 if got, exp := c.Endpoints.Sessions, defaultSessions; got != exp {
359 st.Errorf("Expected Sessions endpoint: '%s', but was: '%s'", exp, got)
360 }
361 })
362 }
363
364 func TestIsAutoCaptureSessions(t *testing.T) {
365 defaultConfig := Configuration{}
366 if !defaultConfig.IsAutoCaptureSessions() {
367 t.Errorf("Expected automatic session tracking to be enabled by default, but was disabled")
368 }
369
370 enabledConfig := Configuration{AutoCaptureSessions: true}
371 if !enabledConfig.IsAutoCaptureSessions() {
372 t.Errorf("Expected automatic session tracking to be enabled when so configured, but was disabled")
373 }
374
375 disabledConfig := Configuration{AutoCaptureSessions: false}
376 if disabledConfig.IsAutoCaptureSessions() {
377 t.Errorf("Expected automatic session tracking to be disabled when so configured, but enabled")
378 }
379 }
0 package device
1
2 import "os"
3
4 var hostname string
5
6 // GetHostname returns the hostname of the current device. Caches the hostname
7 // between calls to ensure this is performant. Returns a blank string in case
8 // that the hostname cannot be identified.
9 func GetHostname() string {
10 if hostname == "" {
11 hostname, _ = os.Hostname()
12 }
13 return hostname
14 }
0 package device
1
2 import (
3 "runtime"
4 )
5
6 // Cached runtime versions that can be updated globally by framework
7 // integrations through AddVersion.
8 var versions *RuntimeVersions
9
10 // RuntimeVersions define the various versions of Go and any framework that may
11 // be in use.
12 // As a user of the notifier you're unlikely to need to modify this struct.
13 // As such, the authors reserve the right to introduce breaking changes to the
14 // properties in this struct. In particular the framework versions are liable
15 // to change in new versions of the notifier in minor/patch versions.
16 type RuntimeVersions struct {
17 Go string `json:"go"`
18
19 Gin string `json:"gin,omitempty"`
20 Martini string `json:"martini,omitempty"`
21 Negroni string `json:"negroni,omitempty"`
22 Revel string `json:"revel,omitempty"`
23 }
24
25 // GetRuntimeVersions retrieves the recorded runtime versions in a goroutine-safe manner.
26 func GetRuntimeVersions() *RuntimeVersions {
27 if versions == nil {
28 versions = &RuntimeVersions{Go: runtime.Version()}
29 }
30 return versions
31 }
32
33 // AddVersion permits a framework to register its version, assuming it's one of
34 // the officially supported frameworks.
35 func AddVersion(framework, version string) {
36 if versions == nil {
37 versions = &RuntimeVersions{Go: runtime.Version()}
38 }
39 switch framework {
40 case "Martini":
41 versions.Martini = version
42 case "Gin":
43 versions.Gin = version
44 case "Negroni":
45 versions.Negroni = version
46 case "Revel":
47 versions.Revel = version
48 }
49 }
0 package device
1
2 import (
3 "runtime"
4 "testing"
5 )
6
7 func TestPristineRuntimeVersions(t *testing.T) {
8 versions = nil // reset global variable
9 rv := GetRuntimeVersions()
10 for _, tc := range []struct{ name, got, exp string }{
11 {name: "Go", got: rv.Go, exp: runtime.Version()},
12 {name: "Gin", got: rv.Gin, exp: ""},
13 {name: "Martini", got: rv.Martini, exp: ""},
14 {name: "Negroni", got: rv.Negroni, exp: ""},
15 {name: "Revel", got: rv.Revel, exp: ""},
16 } {
17 if tc.got != tc.exp {
18 t.Errorf("expected pristine '%s' runtime version to be '%s' but was '%s'", tc.name, tc.exp, tc.got)
19 }
20 }
21 }
22
23 func TestModifiedRuntimeVersions(t *testing.T) {
24 versions = nil // reset global variable
25 rv := GetRuntimeVersions()
26 AddVersion("Gin", "1.2.1")
27 AddVersion("Martini", "1.0.0")
28 AddVersion("Negroni", "1.0.2")
29 AddVersion("Revel", "0.20.1")
30 for _, tc := range []struct{ name, got, exp string }{
31 {name: "Go", got: rv.Go, exp: runtime.Version()},
32 {name: "Gin", got: rv.Gin, exp: "1.2.1"},
33 {name: "Martini", got: rv.Martini, exp: "1.0.0"},
34 {name: "Negroni", got: rv.Negroni, exp: "1.0.2"},
35 {name: "Revel", got: rv.Revel, exp: "0.20.1"},
36 } {
37 if tc.got != tc.exp {
38 t.Errorf("expected modified '%s' runtime version to be '%s' but was '%s'", tc.name, tc.exp, tc.got)
39 }
40 }
41
42 }
0 /*
1 Package bugsnag captures errors in real-time and reports them to Bugsnag (http://bugsnag.com).
2
3 Using bugsnag-go is a three-step process.
4
5 1. As early as possible in your program configure the notifier with your APIKey. This sets up
6 handling of panics that would otherwise crash your app.
7
8 func init() {
9 bugsnag.Configure(bugsnag.Configuration{
10 APIKey: "YOUR_API_KEY_HERE",
11 })
12 }
13
14 2. Add bugsnag to places that already catch panics. For example you should add it to the HTTP server
15 when you call ListenAndServer:
16
17 http.ListenAndServe(":8080", bugsnag.Handler(nil))
18
19 If that's not possible, you can also wrap each
20 HTTP handler manually:
21
22 http.HandleFunc("/" bugsnag.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
23 ...
24 })
25
26 3. To notify Bugsnag of an error that is not a panic, pass it to bugsnag.Notify. This will also
27 log the error message using the configured Logger.
28
29 if err != nil {
30 bugsnag.Notify(err)
31 }
32
33 For detailed integration instructions see https://bugsnag.com/docs/notifiers/go.
34
35 Configuration
36
37 The only required configuration is the Bugsnag API key which can be obtained by clicking "Settings"
38 on the top of https://bugsnag.com/ after signing up. We also recommend you set the ReleaseStage,
39 AppType, and AppVersion if these make sense for your deployment workflow.
40
41 RawData
42
43 If you need to attach extra data to Bugsnag notifications you can do that using
44 the rawData mechanism. Most of the functions that send errors to Bugsnag allow
45 you to pass in any number of interface{} values as rawData. The rawData can
46 consist of the Severity, Context, User or MetaData types listed below, and
47 there is also builtin support for *http.Requests.
48
49 bugsnag.Notify(err, bugsnag.SeverityError)
50
51 If you want to add custom tabs to your bugsnag dashboard you can pass any value in as rawData,
52 and then process it into the event's metadata using a bugsnag.OnBeforeNotify() hook.
53
54 bugsnag.Notify(err, account)
55
56 bugsnag.OnBeforeNotify(func (e *bugsnag.Event, c *bugsnag.Configuration) {
57 for datum := range e.RawData {
58 if account, ok := datum.(Account); ok {
59 e.MetaData.Add("account", "name", account.Name)
60 e.MetaData.Add("account", "url", account.URL)
61 }
62 }
63 })
64
65 If necessary you can pass Configuration in as rawData, or modify the Configuration object passed
66 into OnBeforeNotify hooks. Configuration passed in this way only affects the current notification.
67 */
68 package bugsnag
0 package bugsnag
1
2 import (
3 "fmt"
4 "strings"
5 )
6
7 const metadataPrefix string = "BUGSNAG_METADATA_"
8 const metadataPrefixLen int = len(metadataPrefix)
9 const metadataDefaultTab string = "custom"
10
11 type envMetadata struct {
12 tab string
13 key string
14 value string
15 }
16
17 func loadEnvMetadata(environ []string) []envMetadata {
18 metadata := make([]envMetadata, 0)
19 for _, value := range environ {
20 key, value, err := parseEnvironmentPair(value)
21 if err != nil {
22 continue
23 }
24 if keypath, err := parseMetadataKeypath(key); err == nil {
25 tab, key := splitTabKeyValues(keypath)
26 metadata = append(metadata, envMetadata{tab, key, value})
27 }
28 }
29 return metadata
30 }
31
32 func splitTabKeyValues(keypath string) (string, string) {
33 key_components := strings.SplitN(keypath, "_", 2)
34 if len(key_components) > 1 {
35 return key_components[0], key_components[1]
36 }
37 return metadataDefaultTab, keypath
38 }
39
40 func parseMetadataKeypath(key string) (string, error) {
41 if strings.HasPrefix(key, metadataPrefix) && len(key) > metadataPrefixLen {
42 return strings.TrimPrefix(key, metadataPrefix), nil
43 }
44 return "", fmt.Errorf("No metadata prefix found")
45 }
0 package bugsnag
1
2 import "testing"
3
4 func TestParseMetadataKeypath(t *testing.T) {
5 type output struct {
6 keypath string
7 err string
8 }
9 cases := map[string]output{
10 "": {"", "No metadata prefix found"},
11 "BUGSNAG_METADATA_": {"", "No metadata prefix found"},
12 "BUGSNAG_METADATA_key": {"key", ""},
13 "BUGSNAG_METADATA_device_foo": {"device_foo", ""},
14 "BUGSNAG_METADATA_device_foo_two": {"device_foo_two", ""},
15 }
16
17 for input, expected := range cases {
18 keypath, err := parseMetadataKeypath(input)
19 if len(expected.err) > 0 && (err == nil || err.Error() != expected.err) {
20 t.Errorf("expected error with message '%s', got '%v'", expected.err, err)
21 }
22 if expected.keypath != keypath {
23 t.Errorf("expected keypath '%s', got '%s'", expected.keypath, keypath)
24 }
25 }
26 }
27
28 func TestLoadEnvMetadata(t *testing.T) {
29 cases := map[string]envMetadata{
30 "": {"", "", ""},
31 "BUGSNAG_METADATA_Orange=tomato_paste": {"custom", "Orange", "tomato_paste"},
32 "BUGSNAG_METADATA_true_orange=tomato_paste": {"true", "orange", "tomato_paste"},
33 "BUGSNAG_METADATA_color_Orange=tomato_paste": {"color", "Orange", "tomato_paste"},
34 "BUGSNAG_METADATA_color_Orange_hue=tomato_paste": {"color", "Orange_hue", "tomato_paste"},
35 "BUGSNAG_METADATA_crayonColor_Magenta=tomato_paste": {"crayonColor", "Magenta", "tomato_paste"},
36 "BUGSNAG_METADATA_crayonColor_Magenta_hue=tomato_paste": {"crayonColor", "Magenta_hue", "tomato_paste"},
37 }
38
39 for input, expected := range cases {
40 metadata := loadEnvMetadata([]string{input})
41
42 if len(expected.tab) == 0 {
43 for _, m := range metadata {
44 t.Errorf("erroneously added a value for '%s' to tab '%s':'%s'", input, m.tab, m.key)
45 }
46 } else {
47 if len(metadata) != 1 {
48 t.Fatalf("wrong number of metadata elements: %d %v", len(metadata), metadata)
49 }
50 m := metadata[0]
51 if m.tab != expected.tab {
52 t.Errorf("wrong tab '%s'", expected.tab)
53 continue
54 }
55 if m.key != expected.key {
56 t.Errorf("wrong key '%s'", expected.key)
57 continue
58 }
59 if m.value != expected.value {
60 t.Errorf("incorrect value added to keypath: '%s'", m.value)
61 }
62 }
63 }
64 }
0 package bugsnag
1
2 import (
3 "fmt"
4 "strings"
5 )
6
7 func parseEnvironmentPair(pair string) (string, string, error) {
8 components := strings.SplitN(pair, "=", 2)
9 if len(components) < 2 {
10 return "", "", fmt.Errorf("Not a '='-delimited key pair")
11 }
12 return components[0], components[1], nil
13 }
0 package bugsnag
1
2 import (
3 "fmt"
4 "testing"
5 )
6
7 func TestParsePairs(t *testing.T) {
8 type output struct {
9 key, value string
10 err error
11 }
12
13 cases := map[string]output{
14 "":{"", "", fmt.Errorf("Not a '='-delimited key pair")},
15 "key=value":{"key", "value", nil},
16 "key=value=bar":{"key", "value=bar", nil},
17 "something":{"", "", fmt.Errorf("Not a '='-delimited key pair")},
18 }
19 for input, expected := range cases {
20 key, value, err := parseEnvironmentPair(input)
21 if expected.err != nil && (err == nil || err.Error() != expected.err.Error()) {
22 t.Errorf("expected error '%v', got '%v'", expected.err, err)
23 }
24 if key != expected.key || value != expected.value {
25 t.Errorf("expected pair '%s'='%s', got '%s'='%s'", expected.key, expected.value, key, value)
26 }
27 }
28 }
0 Adds stacktraces to errors in golang.
1
2 This was made to help build the Bugsnag notifier but can be used standalone if
3 you like to have stacktraces on errors.
4
5 See [Godoc](https://godoc.org/github.com/bugsnag/bugsnag-go/v2/errors) for the API docs.
0 // Package errors provides errors that have stack-traces.
1 package errors
2
3 import (
4 "bytes"
5 "fmt"
6 "github.com/pkg/errors"
7 "reflect"
8 "runtime"
9 )
10
11 // The maximum number of stackframes on any error.
12 var MaxStackDepth = 50
13
14 // Error is an error with an attached stacktrace. It can be used
15 // wherever the builtin error interface is expected.
16 type Error struct {
17 Err error
18 Cause *Error
19 stack []uintptr
20 frames []StackFrame
21 }
22
23 // ErrorWithCallers allows passing in error objects that
24 // also have caller information attached.
25 type ErrorWithCallers interface {
26 Error() string
27 Callers() []uintptr
28 }
29
30 // ErrorWithStackFrames allows the stack to be rebuilt from the stack frames, thus
31 // allowing to use the Error type when the program counter is not available.
32 type ErrorWithStackFrames interface {
33 Error() string
34 StackFrames() []StackFrame
35 }
36
37 type errorWithStack interface {
38 StackTrace() errors.StackTrace
39 Error() string
40 }
41
42 type errorWithCause interface {
43 Unwrap() error
44 }
45
46 // New makes an Error from the given value. If that value is already an
47 // error then it will be used directly, if not, it will be passed to
48 // fmt.Errorf("%v"). The skip parameter indicates how far up the stack
49 // to start the stacktrace. 0 is from the current call, 1 from its caller, etc.
50 func New(e interface{}, skip int) *Error {
51 var err error
52
53 switch e := e.(type) {
54 case *Error:
55 return e
56 case ErrorWithCallers:
57 return &Error{
58 Err: e,
59 stack: e.Callers(),
60 Cause: unwrapCause(e),
61 }
62 case errorWithStack:
63 trace := e.StackTrace()
64 stack := make([]uintptr, len(trace))
65 for i, ptr := range trace {
66 stack[i] = uintptr(ptr) - 1
67 }
68 return &Error{
69 Err: e,
70 Cause: unwrapCause(e),
71 stack: stack,
72 }
73 case ErrorWithStackFrames:
74 stack := make([]uintptr, len(e.StackFrames()))
75 for i, frame := range e.StackFrames() {
76 stack[i] = frame.ProgramCounter
77 }
78 return &Error{
79 Err: e,
80 Cause: unwrapCause(e),
81 stack: stack,
82 frames: e.StackFrames(),
83 }
84 case error:
85 err = e
86 default:
87 err = fmt.Errorf("%v", e)
88 }
89
90 stack := make([]uintptr, MaxStackDepth)
91 length := runtime.Callers(2+skip, stack[:])
92 return &Error{
93 Err: err,
94 Cause: unwrapCause(err),
95 stack: stack[:length],
96 }
97 }
98
99 // Errorf creates a new error with the given message. You can use it
100 // as a drop-in replacement for fmt.Errorf() to provide descriptive
101 // errors in return values.
102 func Errorf(format string, a ...interface{}) *Error {
103 return New(fmt.Errorf(format, a...), 1)
104 }
105
106 // Error returns the underlying error's message.
107 func (err *Error) Error() string {
108 return err.Err.Error()
109 }
110
111 // Callers returns the raw stack frames as returned by runtime.Callers()
112 func (err *Error) Callers() []uintptr {
113 return err.stack[:]
114 }
115
116 // Stack returns the callstack formatted the same way that go does
117 // in runtime/debug.Stack()
118 func (err *Error) Stack() []byte {
119 buf := bytes.Buffer{}
120
121 for _, frame := range err.StackFrames() {
122 buf.WriteString(frame.String())
123 }
124
125 return buf.Bytes()
126 }
127
128 // StackFrames returns an array of frames containing information about the
129 // stack.
130 func (err *Error) StackFrames() []StackFrame {
131 if err.frames == nil {
132 callers := runtime.CallersFrames(err.stack)
133 err.frames = make([]StackFrame, 0, len(err.stack))
134 for frame, more := callers.Next(); more; frame, more = callers.Next() {
135 if frame.Func == nil {
136 // Ignore fully inlined functions
137 continue
138 }
139 pkg, name := packageAndName(frame.Func)
140 err.frames = append(err.frames, StackFrame{
141 function: frame.Func,
142 File: frame.File,
143 LineNumber: frame.Line,
144 Name: name,
145 Package: pkg,
146 ProgramCounter: frame.PC,
147 })
148 }
149 }
150
151 return err.frames
152 }
153
154 // TypeName returns the type this error. e.g. *errors.stringError.
155 func (err *Error) TypeName() string {
156 if p, ok := err.Err.(uncaughtPanic); ok {
157 return p.typeName
158 }
159 if name := reflect.TypeOf(err.Err).String(); len(name) > 0 {
160 return name
161 }
162 return "error"
163 }
164
165 func unwrapCause(err interface{}) *Error {
166 if causer, ok := err.(errorWithCause); ok {
167 cause := causer.Unwrap()
168 if cause == nil {
169 return nil
170 } else if hasStack(cause) { // avoid generating a (duplicate) stack from the current frame
171 return New(cause, 0)
172 } else {
173 return &Error{
174 Err: cause,
175 Cause: unwrapCause(cause),
176 stack: []uintptr{},
177 }
178 }
179 }
180 return nil
181 }
182
183 func hasStack(err error) bool {
184 if _, ok := err.(errorWithStack); ok {
185 return true
186 }
187 if _, ok := err.(ErrorWithStackFrames); ok {
188 return true
189 }
190 if _, ok := err.(ErrorWithCallers); ok {
191 return true
192 }
193 return false
194 }
0 // +build go1.13
1
2 package errors
3
4 import (
5 "fmt"
6 "runtime"
7 "testing"
8 )
9
10 func TestUnwrapErrorsCause(t *testing.T) {
11 _, _, line, ok := runtime.Caller(0) // grab line immediately before error generators
12 err1 := fmt.Errorf("invalid token")
13 err2 := fmt.Errorf("login failed: %w", err1)
14 err3 := fmt.Errorf("terminate process: %w", err2)
15 unwrapped := New(err3, 0)
16 if !ok {
17 t.Fatalf("Something has gone wrong with loading the current stack")
18 }
19 if unwrapped.Error() != "terminate process: login failed: invalid token" {
20 t.Errorf("Failed to unwrap error: %s", unwrapped.Error())
21 }
22 assertStacksMatch(t, []StackFrame{
23 StackFrame{Name: "TestUnwrapErrorsCause", File: "errors/error_fmt_wrap_test.go", LineNumber: line + 4},
24 }, unwrapped.StackFrames())
25 if unwrapped.Cause == nil {
26 t.Fatalf("Failed to capture cause error")
27 }
28 if unwrapped.Cause.Error() != "login failed: invalid token" {
29 t.Errorf("Failed to unwrap cause error: %s", unwrapped.Cause.Error())
30 }
31 if len(unwrapped.Cause.StackFrames()) > 0 {
32 t.Errorf("Did not expect cause to have a stack: %v", unwrapped.Cause.StackFrames())
33 }
34 if unwrapped.Cause.Cause == nil {
35 t.Fatalf("Failed to capture nested cause error")
36 }
37 if len(unwrapped.Cause.Cause.StackFrames()) > 0 {
38 t.Errorf("Did not expect cause to have a stack: %v", unwrapped.Cause.Cause.StackFrames())
39 }
40 if unwrapped.Cause.Cause.Cause != nil {
41 t.Fatalf("Extra cause detected: %v", unwrapped.Cause.Cause.Cause)
42 }
43 }
0 package errors
1
2 import (
3 "bytes"
4 "fmt"
5 "io"
6 "runtime"
7 "strings"
8 "testing"
9
10 "github.com/pkg/errors"
11 )
12
13 // fixture functions doing work to avoid inlining
14 func a(i int) error {
15 if b(i + 5) && b(i + 6) {
16 return nil
17 }
18 return fmt.Errorf("not gonna happen")
19 }
20
21 func b(i int) bool {
22 return c(i+2) > 12
23 }
24
25 // panicking function!
26 func c(i int) int {
27 if i > 3 {
28 panic('a')
29 }
30 return i * i
31 }
32
33 func TestParsePanicStack(t *testing.T) {
34 defer func() {
35 err := New(recover(), 0)
36 if err.Error() != "97" {
37 t.Errorf("Received incorrect error, expected 'a' got '%s'", err.Error())
38 }
39 if err.TypeName() != "*errors.errorString" {
40 t.Errorf("Error type was '%s'", err.TypeName())
41 }
42 for index, frame := range err.StackFrames() {
43 if frame.Func() == nil {
44 t.Errorf("Failed to remove nil frame %d", index)
45 }
46 }
47 expected := []StackFrame{
48 StackFrame{Name: "TestParsePanicStack.func1", File: "errors/error_test.go"},
49 StackFrame{Name: "a", File: "errors/error_test.go", LineNumber: 16},
50 }
51 assertStacksMatch(t, expected, err.StackFrames())
52 }()
53
54 a(1)
55 }
56
57 func TestParseGeneratedStack(t *testing.T) {
58 err := New(fmt.Errorf("e_too_many_colander"), 0)
59 expected := []StackFrame{
60 StackFrame{Name: "TestParseGeneratedStack", File: "errors/error_test.go"},
61 }
62 if err.Error() != "e_too_many_colander" {
63 t.Errorf("Error name was '%s'", err.Error())
64 }
65 if err.TypeName() != "*errors.errorString" {
66 t.Errorf("Error type was '%s'", err.TypeName())
67 }
68 for index, frame := range err.StackFrames() {
69 if frame.Func() == nil {
70 t.Errorf("Failed to remove nil frame %d", index)
71 }
72 }
73 assertStacksMatch(t, expected, err.StackFrames())
74 }
75
76 func TestSkipWorks(t *testing.T) {
77 defer func() {
78 err := New(recover(), 1)
79 if err.Error() != "97" {
80 t.Errorf("Received incorrect error, expected 'a' got '%s'", err.Error())
81 }
82
83 for index, frame := range err.StackFrames() {
84 if frame.Name == "TestSkipWorks.func1" {
85 t.Errorf("Failed to skip frame")
86 }
87 if frame.Func() == nil {
88 t.Errorf("Failed to remove inlined frame %d", index)
89 }
90 }
91
92 expected := []StackFrame{
93 StackFrame{Name: "a", File: "errors/error_test.go", LineNumber: 16},
94 }
95
96 assertStacksMatch(t, expected, err.StackFrames())
97 }()
98
99 a(4)
100 }
101
102 func checkFramesMatch(expected StackFrame, actual StackFrame) bool {
103 if actual.Name != expected.Name {
104 return false
105 }
106 // Not using exact match as it would change depending on whether
107 // the package is being tested within or outside of the $GOPATH
108 if expected.File != "" && !strings.HasSuffix(actual.File, expected.File) {
109 return false
110 }
111 if expected.Package != "" && actual.Package != expected.Package {
112 return false
113 }
114 if expected.LineNumber != 0 && actual.LineNumber != expected.LineNumber {
115 return false
116 }
117 return true
118 }
119
120 func assertStacksMatch(t *testing.T, expected []StackFrame, actual []StackFrame) {
121 var lastmatch int = 0
122 var matched int = 0
123 // loop over the actual stacktrace, checking off expected frames as they
124 // are found. Each one might be in the middle of the stack, but the order
125 // should remain the same.
126 for _, actualFrame := range actual {
127 for index, expectedFrame := range expected {
128 if index < lastmatch {
129 continue
130 }
131 if checkFramesMatch(expectedFrame, actualFrame) {
132 lastmatch = index
133 matched += 1
134 break
135 }
136 }
137 }
138 if matched != len(expected) {
139 t.Fatalf("failed to find matches for %d frames: '%v'\ngot: '%v'", len(expected)-matched, expected[matched:], actual)
140 }
141 }
142
143 type testErrorWithStackFrames struct {
144 Err *Error
145 }
146
147 func (tews *testErrorWithStackFrames) StackFrames() []StackFrame {
148 return tews.Err.StackFrames()
149 }
150
151 func (tews *testErrorWithStackFrames) Error() string {
152 return tews.Err.Error()
153 }
154
155 func TestNewError(t *testing.T) {
156
157 e := func() error {
158 return New("hi", 1)
159 }()
160
161 if e.Error() != "hi" {
162 t.Errorf("Constructor with a string failed")
163 }
164
165 if New(fmt.Errorf("yo"), 0).Error() != "yo" {
166 t.Errorf("Constructor with an error failed")
167 }
168
169 if New(e, 0) != e {
170 t.Errorf("Constructor with an Error failed")
171 }
172
173 if New(nil, 0).Error() != "<nil>" {
174 t.Errorf("Constructor with nil failed")
175 }
176
177 err := New("foo", 0)
178 tews := &testErrorWithStackFrames{
179 Err: err,
180 }
181
182 if bytes.Compare(New(tews, 0).Stack(), err.Stack()) != 0 {
183 t.Errorf("Constructor with ErrorWithStackFrames failed")
184 }
185 }
186
187 func TestUnwrapPkgError(t *testing.T) {
188 _, _, line, ok := runtime.Caller(0) // grab line immediately before error generator
189 top := func() error {
190 err := fmt.Errorf("OH NO")
191 return errors.Wrap(err, "failed") // the correct line for the top of the stack
192 }
193 unwrapped := New(top(), 0) // if errors.StackTrace detection fails, this line will be top of stack
194 if !ok {
195 t.Fatalf("Something has gone wrong with loading the current stack")
196 }
197 if unwrapped.Error() != "failed: OH NO" {
198 t.Errorf("Failed to unwrap error: %s", unwrapped.Error())
199 }
200 expected := []StackFrame{
201 StackFrame{Name: "TestUnwrapPkgError.func1", File: "errors/error_test.go", LineNumber: line + 3},
202 StackFrame{Name: "TestUnwrapPkgError", File: "errors/error_test.go", LineNumber: line + 5},
203 }
204 assertStacksMatch(t, expected, unwrapped.StackFrames())
205 }
206
207 type customErr struct {
208 msg string
209 cause error
210 callers []uintptr
211 }
212
213 func newCustomErr(msg string, cause error) error {
214 callers := make([]uintptr, 8)
215 runtime.Callers(2, callers)
216 return customErr{
217 msg: msg,
218 cause: cause,
219 callers: callers,
220 }
221 }
222
223 func (err customErr) Error() string {
224 return err.msg
225 }
226
227 func (err customErr) Unwrap() error {
228 return err.cause
229 }
230
231 func (err customErr) Callers() []uintptr {
232 return err.callers
233 }
234
235 func TestUnwrapCustomCause(t *testing.T) {
236 _, _, line, ok := runtime.Caller(0) // grab line immediately before error generators
237 err1 := fmt.Errorf("invalid token")
238 err2 := newCustomErr("login failed", err1)
239 err3 := newCustomErr("terminate process", err2)
240 unwrapped := New(err3, 0)
241 if !ok {
242 t.Fatalf("Something has gone wrong with loading the current stack")
243 }
244 if unwrapped.Error() != "terminate process" {
245 t.Errorf("Failed to unwrap error: %s", unwrapped.Error())
246 }
247 if unwrapped.Cause == nil {
248 t.Fatalf("Failed to capture cause error")
249 }
250 assertStacksMatch(t, []StackFrame{
251 StackFrame{Name: "TestUnwrapCustomCause", File: "errors/error_test.go", LineNumber: line + 3},
252 }, unwrapped.StackFrames())
253 if unwrapped.Cause.Error() != "login failed" {
254 t.Errorf("Failed to unwrap cause error: %s", unwrapped.Cause.Error())
255 }
256 if unwrapped.Cause.Cause == nil {
257 t.Fatalf("Failed to capture nested cause error")
258 }
259 assertStacksMatch(t, []StackFrame{
260 StackFrame{Name: "TestUnwrapCustomCause", File: "errors/error_test.go", LineNumber: line + 2},
261 }, unwrapped.Cause.StackFrames())
262 if unwrapped.Cause.Cause.Error() != "invalid token" {
263 t.Errorf("Failed to unwrap nested cause error: %s", unwrapped.Cause.Cause.Error())
264 }
265 if len(unwrapped.Cause.Cause.StackFrames()) > 0 {
266 t.Errorf("Did not expect cause to have a stack: %v", unwrapped.Cause.Cause.StackFrames())
267 }
268 if unwrapped.Cause.Cause.Cause != nil {
269 t.Fatalf("Extra cause detected: %v", unwrapped.Cause.Cause.Cause)
270 }
271 }
272
273 func ExampleErrorf() {
274 for i := 1; i <= 2; i++ {
275 if i%2 == 1 {
276 e := Errorf("can only halve even numbers, got %d", i)
277 fmt.Printf("Error: %+v", e)
278 }
279 }
280 // Output:
281 // Error: can only halve even numbers, got 1
282 }
283
284 func ExampleNew() {
285 // Wrap io.EOF with the current stack-trace and return it
286 e := New(io.EOF, 0)
287 fmt.Printf("%+v", e)
288 // Output:
289 // EOF
290 }
291
292 func ExampleNew_skip() {
293 defer func() {
294 if err := recover(); err != nil {
295 // skip 1 frame (the deferred function) and then return the wrapped err
296 err = New(err, 1)
297 }
298 }()
299 }
0 package errors
1
2 import (
3 "strconv"
4 "strings"
5 )
6
7 type uncaughtPanic struct {
8 typeName string
9 message string
10 }
11
12 func (p uncaughtPanic) Error() string {
13 return p.message
14 }
15
16 // ParsePanic allows you to get an error object from the output of a go program
17 // that panicked. This is particularly useful with https://github.com/mitchellh/panicwrap.
18 func ParsePanic(text string) (*Error, error) {
19 lines := strings.Split(text, "\n")
20 prefixes := []string{"panic:", "fatal error:"}
21
22 state := "start"
23
24 var message string
25 var typeName string
26 var stack []StackFrame
27
28 for i := 0; i < len(lines); i++ {
29 line := lines[i]
30
31 if state == "start" {
32 for _, prefix := range prefixes {
33 if strings.HasPrefix(line, prefix) {
34 message = strings.TrimSpace(strings.TrimPrefix(line, prefix))
35 typeName = prefix[:len(prefix) - 1]
36 state = "seek"
37 break
38 }
39 }
40 if state == "start" {
41 return nil, Errorf("bugsnag.panicParser: Invalid line (no prefix): %s", line)
42 }
43
44 } else if state == "seek" {
45 if strings.HasPrefix(line, "goroutine ") && strings.HasSuffix(line, "[running]:") {
46 state = "parsing"
47 }
48
49 } else if state == "parsing" {
50 if line == "" {
51 state = "done"
52 break
53 }
54 createdBy := false
55 if strings.HasPrefix(line, "created by ") {
56 line = strings.TrimPrefix(line, "created by ")
57 createdBy = true
58 }
59
60 i++
61
62 if i >= len(lines) {
63 return nil, Errorf("bugsnag.panicParser: Invalid line (unpaired): %s", line)
64 }
65
66 frame, err := parsePanicFrame(line, lines[i], createdBy)
67 if err != nil {
68 return nil, err
69 }
70
71 stack = append(stack, *frame)
72 if createdBy {
73 state = "done"
74 break
75 }
76 }
77 }
78
79 if state == "done" || state == "parsing" {
80 return &Error{Err: uncaughtPanic{typeName, message}, frames: stack}, nil
81 }
82 return nil, Errorf("could not parse panic: %v", text)
83 }
84
85 // The lines we're passing look like this:
86 //
87 // main.(*foo).destruct(0xc208067e98)
88 // /0/go/src/github.com/bugsnag/bugsnag-go/pan/main.go:22 +0x151
89 func parsePanicFrame(name string, line string, createdBy bool) (*StackFrame, error) {
90 idx := strings.LastIndex(name, "(")
91 if idx == -1 && !createdBy {
92 return nil, Errorf("bugsnag.panicParser: Invalid line (no call): %s", name)
93 }
94 if idx != -1 {
95 name = name[:idx]
96 }
97 pkg := ""
98
99 if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
100 pkg += name[:lastslash] + "/"
101 name = name[lastslash+1:]
102 }
103 if period := strings.Index(name, "."); period >= 0 {
104 pkg += name[:period]
105 name = name[period+1:]
106 }
107
108 name = strings.Replace(name, "·", ".", -1)
109
110 if !strings.HasPrefix(line, "\t") {
111 return nil, Errorf("bugsnag.panicParser: Invalid line (no tab): %s", line)
112 }
113
114 idx = strings.LastIndex(line, ":")
115 if idx == -1 {
116 return nil, Errorf("bugsnag.panicParser: Invalid line (no line number): %s", line)
117 }
118 file := line[1:idx]
119
120 number := line[idx+1:]
121 if idx = strings.Index(number, " +"); idx > -1 {
122 number = number[:idx]
123 }
124
125 lno, err := strconv.ParseInt(number, 10, 32)
126 if err != nil {
127 return nil, Errorf("bugsnag.panicParser: Invalid line (bad line number): %s", line)
128 }
129
130 return &StackFrame{
131 File: file,
132 LineNumber: int(lno),
133 Package: pkg,
134 Name: name,
135 }, nil
136 }
0 package errors
1
2 import (
3 "reflect"
4 "testing"
5 )
6
7 var createdBy = `panic: hello!
8
9 goroutine 54 [running]:
10 runtime.panic(0x35ce40, 0xc208039db0)
11 /0/c/go/src/pkg/runtime/panic.c:279 +0xf5
12 github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
13 /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
14 net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
15 /0/c/go/src/pkg/net/http/server.go:1698 +0x91
16 created by github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.App.Index
17 /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:14 +0x3e
18
19 goroutine 16 [IO wait]:
20 net.runtime_pollWait(0x911c30, 0x72, 0x0)
21 /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
22 net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
23 /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
24 net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
25 /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
26 net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
27 /0/c/go/src/pkg/net/fd_unix.go:409 +0x343
28 net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
29 /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
30 net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
31 /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
32 github.com/revel/revel.Run(0xe6d9)
33 /0/go/src/github.com/revel/revel/server.go:113 +0x926
34 main.main()
35 /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
36 `
37
38 var normalSplit = `panic: hello!
39
40 goroutine 54 [running]:
41 runtime.panic(0x35ce40, 0xc208039db0)
42 /0/c/go/src/pkg/runtime/panic.c:279 +0xf5
43 github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
44 /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
45 net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
46 /0/c/go/src/pkg/net/http/server.go:1698 +0x91
47
48 goroutine 16 [IO wait]:
49 net.runtime_pollWait(0x911c30, 0x72, 0x0)
50 /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
51 net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
52 /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
53 net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
54 /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
55 net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
56 /0/c/go/src/pkg/net/fd_unix.go:409 +0x343
57 net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
58 /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
59 net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
60 /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
61 github.com/revel/revel.Run(0xe6d9)
62 /0/go/src/github.com/revel/revel/server.go:113 +0x926
63 main.main()
64 /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
65 `
66
67 var lastGoroutine = `panic: hello!
68
69 goroutine 16 [IO wait]:
70 net.runtime_pollWait(0x911c30, 0x72, 0x0)
71 /0/c/go/src/pkg/runtime/netpoll.goc:146 +0x66
72 net.(*pollDesc).Wait(0xc2080ba990, 0x72, 0x0, 0x0)
73 /0/c/go/src/pkg/net/fd_poll_runtime.go:84 +0x46
74 net.(*pollDesc).WaitRead(0xc2080ba990, 0x0, 0x0)
75 /0/c/go/src/pkg/net/fd_poll_runtime.go:89 +0x42
76 net.(*netFD).accept(0xc2080ba930, 0x58be30, 0x0, 0x9103f0, 0x23)
77 /0/c/go/src/pkg/net/fd_unix.go:409 +0x343
78 net.(*TCPListener).AcceptTCP(0xc20803e168, 0x8, 0x0, 0x0)
79 /0/c/go/src/pkg/net/tcpsock_posix.go:234 +0x5d
80 net.(*TCPListener).Accept(0xc20803e168, 0x0, 0x0, 0x0, 0x0)
81 /0/c/go/src/pkg/net/tcpsock_posix.go:244 +0x4b
82 github.com/revel/revel.Run(0xe6d9)
83 /0/go/src/github.com/revel/revel/server.go:113 +0x926
84 main.main()
85 /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/tmp/main.go:109 +0xe1a
86
87 goroutine 54 [running]:
88 runtime.panic(0x35ce40, 0xc208039db0)
89 /0/c/go/src/pkg/runtime/panic.c:279 +0xf5
90 github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers.func·001()
91 /0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go:13 +0x74
92 net/http.(*Server).Serve(0xc20806c780, 0x910c88, 0xc20803e168, 0x0, 0x0)
93 /0/c/go/src/pkg/net/http/server.go:1698 +0x91
94 `
95
96 var result = []StackFrame{
97 StackFrame{File: "/0/c/go/src/pkg/runtime/panic.c", LineNumber: 279, Name: "panic", Package: "runtime"},
98 StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 13, Name: "func.001", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers"},
99 StackFrame{File: "/0/c/go/src/pkg/net/http/server.go", LineNumber: 1698, Name: "(*Server).Serve", Package: "net/http"},
100 }
101
102 var resultCreatedBy = append(result,
103 StackFrame{File: "/0/go/src/github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers/app.go", LineNumber: 14, Name: "App.Index", Package: "github.com/loopj/bugsnag-example-apps/go/revelapp/app/controllers", ProgramCounter: 0x0})
104
105 func TestParsePanic(t *testing.T) {
106
107 todo := map[string]string{
108 "createdBy": createdBy,
109 "normalSplit": normalSplit,
110 "lastGoroutine": lastGoroutine,
111 }
112
113 for key, val := range todo {
114 Err, err := ParsePanic(val)
115
116 if err != nil {
117 t.Fatal(err)
118 }
119
120 if Err.TypeName() != "panic" {
121 t.Errorf("Wrong type: %s", Err.TypeName())
122 }
123
124 if Err.Error() != "hello!" {
125 t.Errorf("Wrong message: %s", Err.TypeName())
126 }
127
128 if Err.StackFrames()[0].Func() != nil {
129 t.Errorf("Somehow managed to find a func...")
130 }
131
132 result := result
133 if key == "createdBy" {
134 result = resultCreatedBy
135 }
136
137 if !reflect.DeepEqual(Err.StackFrames(), result) {
138 t.Errorf("Wrong stack for %s: %#v", key, Err.StackFrames())
139 }
140 }
141 }
142
143 var concurrentMapReadWrite = `fatal error: concurrent map read and map write
144
145 goroutine 1 [running]:
146 runtime.throw(0x10766f5, 0x21)
147 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/panic.go:1116 +0x72 fp=0xc00003a6c8 sp=0xc00003a698 pc=0x102d592
148 runtime.mapaccess1_faststr(0x1066fc0, 0xc000060000, 0x10732e0, 0x1, 0xc000100088)
149 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/map_faststr.go:21 +0x465 fp=0xc00003a738 sp=0xc00003a6c8 pc=0x100e9c5
150 main.concurrentWrite()
151 /myapps/go/fatalerror/main.go:14 +0x7a fp=0xc00003a778 sp=0xc00003a738 pc=0x105d83a
152 main.main()
153 /myapps/go/fatalerror/main.go:41 +0x25 fp=0xc00003a788 sp=0xc00003a778 pc=0x105d885
154 runtime.main()
155 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/proc.go:204 +0x209 fp=0xc00003a7e0 sp=0xc00003a788 pc=0x102fd49
156 runtime.goexit()
157 /usr/local/Cellar/go/1.15.5/libexec/src/runtime/asm_amd64.s:1374 +0x1 fp=0xc00003a7e8 sp=0xc00003a7e0 pc=0x105a4a1
158
159 goroutine 5 [runnable]:
160 main.concurrentWrite.func1(0xc000060000)
161 /myapps/go/fatalerror/main.go:10 +0x4c
162 created by main.concurrentWrite
163 /myapps/go/fatalerror/main.go:8 +0x4b
164 `
165
166 func TestParseFatalError(t *testing.T) {
167
168 Err, err := ParsePanic(concurrentMapReadWrite)
169
170 if err != nil {
171 t.Fatal(err)
172 }
173
174 if Err.TypeName() != "fatal error" {
175 t.Errorf("Wrong type: %s", Err.TypeName())
176 }
177
178 if Err.Error() != "concurrent map read and map write" {
179 t.Errorf("Wrong message: '%s'", Err.Error())
180 }
181
182 if Err.StackFrames()[0].Func() != nil {
183 t.Errorf("Somehow managed to find a func...")
184 }
185
186 var result = []StackFrame{
187 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/panic.go", LineNumber: 1116, Name: "throw", Package: "runtime"},
188 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/map_faststr.go", LineNumber: 21, Name: "mapaccess1_faststr", Package: "runtime"},
189 StackFrame{File: "/myapps/go/fatalerror/main.go", LineNumber: 14, Name: "concurrentWrite", Package: "main"},
190 StackFrame{File: "/myapps/go/fatalerror/main.go", LineNumber: 41, Name: "main", Package: "main"},
191 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/proc.go", LineNumber: 204, Name: "main", Package: "runtime"},
192 StackFrame{File: "/usr/local/Cellar/go/1.15.5/libexec/src/runtime/asm_amd64.s", LineNumber: 1374, Name: "goexit", Package: "runtime"},
193 }
194
195 if !reflect.DeepEqual(Err.StackFrames(), result) {
196 t.Errorf("Wrong stack for concurrent write fatal error:")
197 for i, frame := range result {
198 t.Logf("[%d] %#v", i, frame)
199 if len(Err.StackFrames()) > i {
200 t.Logf(" %#v", Err.StackFrames()[i])
201 }
202 }
203 }
204 }
0 package errors
1
2 import (
3 "bytes"
4 "fmt"
5 "io/ioutil"
6 "runtime"
7 "strings"
8 )
9
10 // A StackFrame contains all necessary information about to generate a line
11 // in a callstack.
12 type StackFrame struct {
13 File string
14 LineNumber int
15 Name string
16 Package string
17 ProgramCounter uintptr
18 function *runtime.Func
19 }
20
21 // NewStackFrame popoulates a stack frame object from the program counter.
22 func NewStackFrame(pc uintptr) (frame StackFrame) {
23
24 frame = StackFrame{ProgramCounter: pc}
25 if frame.Func() == nil {
26 return
27 }
28 frame.Package, frame.Name = packageAndName(frame.Func())
29
30 // pc -1 because the program counters we use are usually return addresses,
31 // and we want to show the line that corresponds to the function call
32 frame.File, frame.LineNumber = frame.Func().FileLine(pc - 1)
33 return
34
35 }
36
37 // Func returns the function that this stackframe corresponds to
38 func (frame *StackFrame) Func() *runtime.Func {
39 return frame.function
40 }
41
42 // String returns the stackframe formatted in the same way as go does
43 // in runtime/debug.Stack()
44 func (frame *StackFrame) String() string {
45 str := fmt.Sprintf("%s:%d (0x%x)\n", frame.File, frame.LineNumber, frame.ProgramCounter)
46
47 source, err := frame.SourceLine()
48 if err != nil {
49 return str
50 }
51
52 return str + fmt.Sprintf("\t%s: %s\n", frame.Name, source)
53 }
54
55 // SourceLine gets the line of code (from File and Line) of the original source if possible
56 func (frame *StackFrame) SourceLine() (string, error) {
57 data, err := ioutil.ReadFile(frame.File)
58
59 if err != nil {
60 return "", err
61 }
62
63 lines := bytes.Split(data, []byte{'\n'})
64 if frame.LineNumber <= 0 || frame.LineNumber >= len(lines) {
65 return "???", nil
66 }
67 // -1 because line-numbers are 1 based, but our array is 0 based
68 return string(bytes.Trim(lines[frame.LineNumber-1], " \t")), nil
69 }
70
71 func packageAndName(fn *runtime.Func) (string, string) {
72 name := fn.Name()
73 pkg := ""
74
75 // The name includes the path name to the package, which is unnecessary
76 // since the file name is already included. Plus, it has center dots.
77 // That is, we see
78 // runtime/debug.*T·ptrmethod
79 // and want
80 // *T.ptrmethod
81 // Since the package path might contains dots (e.g. code.google.com/...),
82 // we first remove the path prefix if there is one.
83 if lastslash := strings.LastIndex(name, "/"); lastslash >= 0 {
84 pkg += name[:lastslash] + "/"
85 name = name[lastslash+1:]
86 }
87 if period := strings.Index(name, "."); period >= 0 {
88 pkg += name[:period]
89 name = name[period+1:]
90 }
91
92 name = strings.Replace(name, "·", ".", -1)
93 return pkg, name
94 }
0 package bugsnag
1
2 import (
3 "context"
4 "net/http"
5 "strings"
6
7 "github.com/bugsnag/bugsnag-go/v2/errors"
8 )
9
10 // Context is the context of the error in Bugsnag.
11 // This can be passed to Notify, Recover or AutoNotify as rawData.
12 type Context struct {
13 String string
14 }
15
16 // User represents the searchable user-data on Bugsnag. The Id is also used
17 // to determine the number of users affected by a bug. This can be
18 // passed to Notify, Recover or AutoNotify as rawData.
19 type User struct {
20 Id string `json:"id,omitempty"`
21 Name string `json:"name,omitempty"`
22 Email string `json:"email,omitempty"`
23 }
24
25 // ErrorClass overrides the error class in Bugsnag.
26 // This struct enables you to group errors as you like.
27 type ErrorClass struct {
28 Name string
29 }
30
31 // Sets the severity of the error on Bugsnag. These values can be
32 // passed to Notify, Recover or AutoNotify as rawData.
33 var (
34 SeverityError = severity{"error"}
35 SeverityWarning = severity{"warning"}
36 SeverityInfo = severity{"info"}
37 )
38
39 // The severity tag type, private so that people can only use Error,Warning,Info
40 type severity struct {
41 String string
42 }
43
44 // The form of stacktrace that Bugsnag expects
45 type StackFrame struct {
46 Method string `json:"method"`
47 File string `json:"file"`
48 LineNumber int `json:"lineNumber"`
49 InProject bool `json:"inProject,omitempty"`
50 }
51
52 type SeverityReason string
53
54 const (
55 SeverityReasonCallbackSpecified SeverityReason = "userCallbackSetSeverity"
56 SeverityReasonHandledError = "handledError"
57 SeverityReasonHandledPanic = "handledPanic"
58 SeverityReasonUnhandledError = "unhandledError"
59 SeverityReasonUnhandledMiddlewareError = "unhandledErrorMiddleware"
60 SeverityReasonUnhandledPanic = "unhandledPanic"
61 SeverityReasonUserSpecified = "userSpecifiedSeverity"
62 )
63
64 type HandledState struct {
65 SeverityReason SeverityReason
66 OriginalSeverity severity
67 Unhandled bool
68 Framework string
69 }
70
71 // Event represents a payload of data that gets sent to Bugsnag.
72 // This is passed to each OnBeforeNotify hook.
73 type Event struct {
74
75 // The original error that caused this event, not sent to Bugsnag.
76 Error *errors.Error
77
78 // The rawData affecting this error, not sent to Bugsnag.
79 RawData []interface{}
80
81 // The error class to be sent to Bugsnag. This defaults to the type name of the Error, for
82 // example *error.String
83 ErrorClass string
84 // The error message to be sent to Bugsnag. This defaults to the return value of Error.Error()
85 Message string
86 // The stacktrrace of the error to be sent to Bugsnag.
87 Stacktrace []StackFrame
88
89 // The context to be sent to Bugsnag. This should be set to the part of the app that was running,
90 // e.g. for http requests, set it to the path.
91 Context string
92 // The severity of the error. Can be SeverityError, SeverityWarning or SeverityInfo.
93 Severity severity
94 // The grouping hash is used to override Bugsnag's grouping. Set this if you'd like all errors with
95 // the same grouping hash to group together in the dashboard.
96 GroupingHash string
97
98 // User data to send to Bugsnag. This is searchable on the dashboard.
99 User *User
100 // Other MetaData to send to Bugsnag. Appears as a set of tabbed tables in the dashboard.
101 MetaData MetaData
102 // Ctx is the context of the session the event occurred in. This allows Bugsnag to associate the event with the session.
103 Ctx context.Context
104 // Request is the request information that populates the Request tab in the dashboard.
105 Request *RequestJSON
106 // The reason for the severity and original value
107 handledState HandledState
108 // True if the event was caused by an automatic event
109 Unhandled bool
110 }
111
112 func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration) {
113 config := notifier.Config
114 event := &Event{
115 RawData: append(notifier.RawData, rawData...),
116 Severity: SeverityWarning,
117 MetaData: make(MetaData),
118 handledState: HandledState{
119 SeverityReason: SeverityReasonHandledError,
120 OriginalSeverity: SeverityWarning,
121 Unhandled: false,
122 Framework: "",
123 },
124 Unhandled: false,
125 }
126
127 var err *errors.Error
128 var callbacks []func(*Event)
129
130 for _, datum := range event.RawData {
131 switch datum := datum.(type) {
132
133 case error, errors.Error:
134 err = errors.New(datum.(error), 1)
135 event.Error = err
136 // Only assign automatically if not explicitly set through ErrorClass already
137 if event.ErrorClass == "" {
138 event.ErrorClass = err.TypeName()
139 }
140 event.Message = err.Error()
141 event.Stacktrace = make([]StackFrame, len(err.StackFrames()))
142
143 case bool:
144 config = config.merge(&Configuration{Synchronous: bool(datum)})
145
146 case severity:
147 event.Severity = datum
148 event.handledState.OriginalSeverity = datum
149 event.handledState.SeverityReason = SeverityReasonUserSpecified
150
151 case Context:
152 event.Context = datum.String
153
154 case context.Context:
155 populateEventWithContext(datum, event)
156
157 case *http.Request:
158 populateEventWithRequest(datum, event)
159
160 case Configuration:
161 config = config.merge(&datum)
162
163 case MetaData:
164 event.MetaData.Update(datum)
165
166 case User:
167 event.User = &datum
168
169 case ErrorClass:
170 event.ErrorClass = datum.Name
171
172 case HandledState:
173 event.handledState = datum
174 event.Severity = datum.OriginalSeverity
175 event.Unhandled = datum.Unhandled
176 case func(*Event):
177 callbacks = append(callbacks, datum)
178 }
179 }
180
181 event.Stacktrace = generateStacktrace(err, config)
182
183 for _, callback := range callbacks {
184 callback(event)
185 if event.Severity != event.handledState.OriginalSeverity {
186 event.handledState.SeverityReason = SeverityReasonCallbackSpecified
187 }
188 }
189
190 return event, config
191 }
192
193 func generateStacktrace(err *errors.Error, config *Configuration) []StackFrame {
194 stack := make([]StackFrame, len(err.StackFrames()))
195 for i, frame := range err.StackFrames() {
196 file := frame.File
197 inProject := config.isProjectPackage(frame.Package)
198
199 // remove $GOROOT and $GOHOME from other frames
200 if idx := strings.Index(file, frame.Package); idx > -1 {
201 file = file[idx:]
202 }
203 if inProject {
204 file = config.stripProjectPackages(file)
205 }
206
207 stack[i] = StackFrame{
208 Method: frame.Name,
209 File: file,
210 LineNumber: frame.LineNumber,
211 InProject: inProject,
212 }
213 }
214
215 return stack
216 }
217
218 func populateEventWithContext(ctx context.Context, event *Event) {
219 event.Ctx = ctx
220 reqJSON, req := extractRequestInfo(ctx)
221 if event.Request == nil {
222 event.Request = reqJSON
223 }
224 populateEventWithRequest(req, event)
225
226 }
227
228 func populateEventWithRequest(req *http.Request, event *Event) {
229 if req == nil {
230 return
231 }
232
233 event.Request = extractRequestInfoFromReq(req)
234
235 if event.Context == "" {
236 event.Context = req.URL.Path
237 }
238
239 // Default user.id to IP so that the count of users affected works.
240 if event.User == nil {
241 ip := req.RemoteAddr
242 if idx := strings.LastIndex(ip, ":"); idx != -1 {
243 ip = ip[:idx]
244 }
245 event.User = &User{Id: ip}
246 }
247 }
0 package bugsnag
1
2 import (
3 "context"
4 "net/http"
5 "net/http/httptest"
6 "reflect"
7 "strings"
8 "testing"
9 )
10
11 func TestPopulateEvent(t *testing.T) {
12 event := new(Event)
13 contexts := make(chan context.Context, 1)
14 reqs := make(chan *http.Request, 1)
15 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16 contexts <- AttachRequestData(r.Context(), r)
17 reqs <- r
18 }))
19 defer ts.Close()
20
21 http.Get(ts.URL + "/serenity?q=abcdef")
22
23 ctx, req := <-contexts, <-reqs
24 populateEventWithContext(ctx, event)
25
26 for _, tc := range []struct{ e, c interface{} }{
27 {e: event.Ctx, c: ctx},
28 {e: event.Request, c: extractRequestInfoFromReq(req)},
29 {e: event.Context, c: req.URL.Path},
30 {e: event.User.Id, c: req.RemoteAddr[:strings.LastIndex(req.RemoteAddr, ":")]},
31 } {
32 if !reflect.DeepEqual(tc.e, tc.c) {
33 t.Errorf("Expected '%+v' and '%+v' to be equal", tc.e, tc.c)
34 }
35 }
36 }
0 module github.com/bugsnag/bugsnag-go/v2
1
2 go 1.15
3
4 require (
5 github.com/bitly/go-simplejson v0.5.0
6 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
7 github.com/bugsnag/panicwrap v1.3.4
8 github.com/gofrs/uuid v4.0.0+incompatible
9 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
10 github.com/kr/pretty v0.2.1 // indirect
11 github.com/pkg/errors v0.9.1
12 )
0 github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
1 github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
2 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
3 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
4 github.com/bugsnag/panicwrap v1.3.4 h1:A6sXFtDGsgU/4BLf5JT0o5uYg3EeKgGx3Sfs+/uk3pU=
5 github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
6 github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
7 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
8 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
9 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
10 github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
11 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
12 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
13 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
14 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
15 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
16 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
0 package headers
1
2 import "time"
3
4 //PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for
5 //API key, payload version, and the time at which the request is being sent.
6 func PrefixedHeaders(apiKey, payloadVersion string) map[string]string {
7 return map[string]string{
8 "Content-Type": "application/json",
9 "Bugsnag-Api-Key": apiKey,
10 "Bugsnag-Payload-Version": payloadVersion,
11 "Bugsnag-Sent-At": time.Now().UTC().Format(time.RFC3339),
12 }
13 }
0 package headers
1
2 import (
3 "strings"
4 "testing"
5 "time"
6 )
7
8 const APIKey = "abcd1234abcd1234"
9 const testPayloadVersion = "3"
10
11 func TestConstantBugsnagPrefixedHeaders(t *testing.T) {
12 headers := PrefixedHeaders(APIKey, testPayloadVersion)
13 testCases := []struct {
14 header string
15 expected string
16 }{
17 {header: "Content-Type", expected: "application/json"},
18 {header: "Bugsnag-Api-Key", expected: APIKey},
19 {header: "Bugsnag-Payload-Version", expected: testPayloadVersion},
20 }
21 for _, tc := range testCases {
22 t.Run(tc.header, func(st *testing.T) {
23 if got := headers[tc.header]; got != tc.expected {
24 t.Errorf("Expected headers to contain %s header %s but was %s", tc.header, tc.expected, got)
25 }
26 })
27 }
28 }
29
30 func TestTimeDependentBugsnagPrefixedHeaders(t *testing.T) {
31 headers := PrefixedHeaders(APIKey, testPayloadVersion)
32 sentAtString := headers["Bugsnag-Sent-At"]
33 if !strings.HasSuffix(sentAtString, "Z") {
34 t.Errorf("Error when setting Bugsnag-Sent-At header: %s, doesn't end with a Z", sentAtString)
35 }
36 sentAt, err := time.Parse(time.RFC3339, sentAtString)
37
38 if err != nil {
39 t.Errorf("Error when attempting to parse Bugsnag-Sent-At header: %s", sentAtString)
40 }
41
42 if now := time.Now(); now.Sub(sentAt) > time.Second || now.Sub(sentAt) < -time.Second {
43 t.Errorf("Expected Bugsnag-Sent-At header approx. %s but was %s", now.UTC().Format(time.RFC3339), sentAtString)
44 }
45 }
0 // The code is stripped from:
1 // http://golang.org/src/pkg/encoding/json/tags.go?m=text
2
3 package bugsnag
4
5 import (
6 "strings"
7 )
8
9 // tagOptions is the string following a comma in a struct field's "json"
10 // tag, or the empty string. It does not include the leading comma.
11 type tagOptions string
12
13 // parseTag splits a struct field's json tag into its name and
14 // comma-separated options.
15 func parseTag(tag string) (string, tagOptions) {
16 if idx := strings.Index(tag, ","); idx != -1 {
17 return tag[:idx], tagOptions(tag[idx+1:])
18 }
19 return tag, tagOptions("")
20 }
21
22 // Contains reports whether a comma-separated list of options
23 // contains a particular substr flag. substr must be surrounded by a
24 // string boundary or commas.
25 func (o tagOptions) Contains(optionName string) bool {
26 if len(o) == 0 {
27 return false
28 }
29 s := string(o)
30 for s != "" {
31 var next string
32 i := strings.Index(s, ",")
33 if i >= 0 {
34 s, next = s[:i], s[i+1:]
35 }
36 if s == optionName {
37 return true
38 }
39 s = next
40 }
41 return false
42 }
0 package bugsnag
1
2 import (
3 "fmt"
4 "reflect"
5 "strings"
6 )
7
8 // MetaData is added to the Bugsnag dashboard in tabs. Each tab is
9 // a map of strings -> values. You can pass MetaData to Notify, Recover
10 // and AutoNotify as rawData.
11 type MetaData map[string]map[string]interface{}
12
13 // Update the meta-data with more information. Tabs are merged together such
14 // that unique keys from both sides are preserved, and duplicate keys end up
15 // with the provided values.
16 func (meta MetaData) Update(other MetaData) {
17 for name, tab := range other {
18
19 if meta[name] == nil {
20 meta[name] = make(map[string]interface{})
21 }
22
23 for key, value := range tab {
24 meta[name][key] = value
25 }
26 }
27 }
28
29 // Add creates a tab of Bugsnag meta-data.
30 // If the tab doesn't yet exist it will be created.
31 // If the key already exists, it will be overwritten.
32 func (meta MetaData) Add(tab string, key string, value interface{}) {
33 if meta[tab] == nil {
34 meta[tab] = make(map[string]interface{})
35 }
36
37 meta[tab][key] = value
38 }
39
40 // AddStruct creates a tab of Bugsnag meta-data.
41 // The struct will be converted to an Object using the
42 // reflect library so any private fields will not be exported.
43 // As a safety measure, if you pass a non-struct the value will be
44 // sent to Bugsnag under the "Extra data" tab.
45 func (meta MetaData) AddStruct(tab string, obj interface{}) {
46 val := sanitizer{}.Sanitize(obj)
47 content, ok := val.(map[string]interface{})
48 if ok {
49 meta[tab] = content
50 } else {
51 // Wasn't a struct
52 meta.Add("Extra data", tab, obj)
53 }
54
55 }
56
57 // Remove any values from meta-data that have keys matching the filters,
58 // and any that are recursive data-structures
59 func (meta MetaData) sanitize(filters []string) interface{} {
60 return sanitizer{
61 Filters: filters,
62 Seen: make([]interface{}, 0),
63 }.Sanitize(meta)
64
65 }
66
67 // The sanitizer is used to remove filtered params and recursion from meta-data.
68 type sanitizer struct {
69 Filters []string
70 Seen []interface{}
71 }
72
73 func (s sanitizer) Sanitize(data interface{}) interface{} {
74 for _, s := range s.Seen {
75 // TODO: we don't need deep equal here, just type-ignoring equality
76 if reflect.DeepEqual(data, s) {
77 return "[RECURSION]"
78 }
79 }
80
81 // Sanitizers are passed by value, so we can modify s and it only affects
82 // s.Seen for nested calls.
83 s.Seen = append(s.Seen, data)
84
85 t := reflect.TypeOf(data)
86 v := reflect.ValueOf(data)
87
88 if t == nil {
89 return "<nil>"
90 }
91
92 switch t.Kind() {
93 case reflect.Bool,
94 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
95 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
96 reflect.Float32, reflect.Float64:
97 return data
98
99 case reflect.String:
100 return data
101
102 case reflect.Interface, reflect.Ptr:
103 if v.IsNil() {
104 return "<nil>"
105 }
106 return s.Sanitize(v.Elem().Interface())
107
108 case reflect.Array, reflect.Slice:
109 ret := make([]interface{}, v.Len())
110 for i := 0; i < v.Len(); i++ {
111 ret[i] = s.Sanitize(v.Index(i).Interface())
112 }
113 return ret
114
115 case reflect.Map:
116 return s.sanitizeMap(v)
117
118 case reflect.Struct:
119 return s.sanitizeStruct(v, t)
120
121 // Things JSON can't serialize:
122 // case t.Chan, t.Func, reflect.Complex64, reflect.Complex128, reflect.UnsafePointer:
123 default:
124 return "[" + t.String() + "]"
125
126 }
127
128 }
129
130 func (s sanitizer) sanitizeMap(v reflect.Value) interface{} {
131 ret := make(map[string]interface{})
132
133 for _, key := range v.MapKeys() {
134 val := s.Sanitize(v.MapIndex(key).Interface())
135 newKey := fmt.Sprintf("%v", key.Interface())
136
137 if s.shouldRedact(newKey) {
138 val = "[FILTERED]"
139 }
140
141 ret[newKey] = val
142 }
143
144 return ret
145 }
146
147 func (s sanitizer) sanitizeStruct(v reflect.Value, t reflect.Type) interface{} {
148 ret := make(map[string]interface{})
149
150 for i := 0; i < v.NumField(); i++ {
151
152 val := v.Field(i)
153 // Don't export private fields
154 if !val.CanInterface() {
155 continue
156 }
157
158 name := t.Field(i).Name
159 var opts tagOptions
160
161 // Parse JSON tags. Supports name and "omitempty"
162 if jsonTag := t.Field(i).Tag.Get("json"); len(jsonTag) != 0 {
163 name, opts = parseTag(jsonTag)
164 }
165
166 if s.shouldRedact(name) {
167 ret[name] = "[FILTERED]"
168 } else {
169 sanitized := s.Sanitize(val.Interface())
170 if str, ok := sanitized.(string); ok {
171 if !(opts.Contains("omitempty") && len(str) == 0) {
172 ret[name] = str
173 }
174 } else {
175 ret[name] = sanitized
176 }
177
178 }
179 }
180
181 return ret
182 }
183
184 func (s sanitizer) shouldRedact(key string) bool {
185 for _, filter := range s.Filters {
186 if strings.Contains(strings.ToLower(key), strings.ToLower(filter)) {
187 return true
188 }
189 }
190 return false
191 }
0 package bugsnag
1
2 import (
3 "reflect"
4 "testing"
5 "unsafe"
6
7 "github.com/bugsnag/bugsnag-go/v2/errors"
8 )
9
10 type _account struct {
11 ID string
12 Name string
13 Plan struct {
14 Premium bool
15 }
16 Password string
17 secret string
18 Email string `json:"email"`
19 EmptyEmail string `json:"emptyemail,omitempty"`
20 NotEmptyEmail string `json:"not_empty_email,omitempty"`
21 }
22
23 type _broken struct {
24 Me *_broken
25 Data string
26 }
27
28 var account = _account{}
29 var notifier = New(Configuration{})
30
31 func TestMetaDataAdd(t *testing.T) {
32 m := MetaData{
33 "one": {
34 "key": "value",
35 "override": false,
36 }}
37
38 m.Add("one", "override", true)
39 m.Add("one", "new", "key")
40 m.Add("new", "tab", account)
41
42 m.AddStruct("lol", "not really a struct")
43 m.AddStruct("account", account)
44
45 if !reflect.DeepEqual(m, MetaData{
46 "one": {
47 "key": "value",
48 "override": true,
49 "new": "key",
50 },
51 "new": {
52 "tab": account,
53 },
54 "Extra data": {
55 "lol": "not really a struct",
56 },
57 "account": {
58 "ID": "",
59 "Name": "",
60 "Plan": map[string]interface{}{
61 "Premium": false,
62 },
63 "Password": "",
64 "email": "",
65 },
66 }) {
67 t.Errorf("metadata.Add didn't work: %#v", m)
68 }
69 }
70
71 func TestMetaDataUpdate(t *testing.T) {
72
73 m := MetaData{
74 "one": {
75 "key": "value",
76 "override": false,
77 }}
78
79 m.Update(MetaData{
80 "one": {
81 "override": true,
82 "new": "key",
83 },
84 "new": {
85 "tab": account,
86 },
87 })
88
89 if !reflect.DeepEqual(m, MetaData{
90 "one": {
91 "key": "value",
92 "override": true,
93 "new": "key",
94 },
95 "new": {
96 "tab": account,
97 },
98 }) {
99 t.Errorf("metadata.Update didn't work: %#v", m)
100 }
101 }
102
103 func TestMetaDataSanitize(t *testing.T) {
104
105 var broken = _broken{}
106 broken.Me = &broken
107 broken.Data = "ohai"
108 account.Name = "test"
109 account.ID = "test"
110 account.secret = "hush"
111 account.Email = "example@example.com"
112 account.EmptyEmail = ""
113 account.NotEmptyEmail = "not_empty_email@example.com"
114
115 m := MetaData{
116 "one": {
117 "bool": true,
118 "int": 7,
119 "float": 7.1,
120 "complex": complex(1, 1),
121 "func": func() {},
122 "unsafe": unsafe.Pointer(broken.Me),
123 "string": "string",
124 "password": "secret",
125 "array": []hash{{
126 "creditcard": "1234567812345678",
127 "broken": broken,
128 }},
129 "broken": broken,
130 "account": account,
131 },
132 }
133
134 n := m.sanitize([]string{"password", "creditcard"})
135
136 if !reflect.DeepEqual(n, map[string]interface{}{
137 "one": map[string]interface{}{
138 "bool": true,
139 "int": 7,
140 "float": 7.1,
141 "complex": "[complex128]",
142 "string": "string",
143 "unsafe": "[unsafe.Pointer]",
144 "func": "[func()]",
145 "password": "[FILTERED]",
146 "array": []interface{}{map[string]interface{}{
147 "creditcard": "[FILTERED]",
148 "broken": map[string]interface{}{
149 "Me": "[RECURSION]",
150 "Data": "ohai",
151 },
152 }},
153 "broken": map[string]interface{}{
154 "Me": "[RECURSION]",
155 "Data": "ohai",
156 },
157 "account": map[string]interface{}{
158 "ID": "test",
159 "Name": "test",
160 "Plan": map[string]interface{}{
161 "Premium": false,
162 },
163 "Password": "[FILTERED]",
164 "email": "example@example.com",
165 "not_empty_email": "not_empty_email@example.com",
166 },
167 },
168 }) {
169 t.Errorf("metadata.Sanitize didn't work: %#v", n)
170 }
171
172 }
173
174 func TestSanitizerSanitize(t *testing.T) {
175 var (
176 nilPointer *int
177 nilInterface = interface{}(nil)
178 )
179
180 for n, tc := range []struct {
181 input interface{}
182 want interface{}
183 }{
184 {nilPointer, "<nil>"},
185 {nilInterface, "<nil>"},
186 } {
187 s := &sanitizer{}
188 gotValue := s.Sanitize(tc.input)
189
190 if got, want := gotValue, tc.want; got != want {
191 t.Errorf("[%d] got %v, want %v", n, got, want)
192 }
193 }
194 }
195
196 func ExampleMetaData() {
197 notifier.Notify(errors.Errorf("hi world"),
198 MetaData{"Account": {
199 "id": account.ID,
200 "name": account.Name,
201 "paying?": account.Plan.Premium,
202 }})
203 }
0 package bugsnag
1
2 import (
3 "net/http"
4 )
5
6 type (
7 beforeFunc func(*Event, *Configuration) error
8
9 // MiddlewareStacks keep middleware in the correct order. They are
10 // called in reverse order, so if you add a new middleware it will
11 // be called before all existing middleware.
12 middlewareStack struct {
13 before []beforeFunc
14 }
15 )
16
17 // AddMiddleware adds a new middleware to the outside of the existing ones,
18 // when the middlewareStack is Run it will be run before all middleware that
19 // have been added before.
20 func (stack *middlewareStack) OnBeforeNotify(middleware beforeFunc) {
21 stack.before = append(stack.before, middleware)
22 }
23
24 // Run causes all the middleware to be run. If they all permit it the next callback
25 // will be called with all the middleware on the stack.
26 func (stack *middlewareStack) Run(event *Event, config *Configuration, next func() error) error {
27 // run all the before filters in reverse order
28 for i := range stack.before {
29 before := stack.before[len(stack.before)-i-1]
30
31 severity := event.Severity
32 err := stack.runBeforeFilter(before, event, config)
33 if err != nil {
34 return err
35 }
36 if event.Severity != severity {
37 event.handledState.SeverityReason = SeverityReasonCallbackSpecified
38 }
39 }
40
41 return next()
42 }
43
44 func (stack *middlewareStack) runBeforeFilter(f beforeFunc, event *Event, config *Configuration) error {
45 defer func() {
46 if err := recover(); err != nil {
47 config.logf("bugsnag/middleware: unexpected panic: %v", err)
48 }
49 }()
50
51 return f(event, config)
52 }
53
54 // httpRequestMiddleware is added OnBeforeNotify by default. It takes information
55 // from an http.Request passed in as rawData, and adds it to the Event. You can
56 // use this as a template for writing your own Middleware.
57 func httpRequestMiddleware(event *Event, config *Configuration) error {
58 for _, datum := range event.RawData {
59 if request, ok := datum.(*http.Request); ok {
60 event.MetaData.Update(MetaData{
61 "request": {
62 "params": request.URL.Query(),
63 },
64 })
65 }
66 }
67 return nil
68 }
0 package bugsnag
1
2 import (
3 "bytes"
4 "fmt"
5 "github.com/bugsnag/bugsnag-go/v2/errors"
6 "log"
7 "reflect"
8 "testing"
9 )
10
11 func TestMiddlewareOrder(t *testing.T) {
12
13 err := fmt.Errorf("test")
14 data := []interface{}{errors.New(err, 1)}
15 event, config := newEvent(data, &defaultNotifier)
16
17 result := make([]int, 0, 7)
18 stack := middlewareStack{}
19 stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
20 result = append(result, 2)
21 return nil
22 })
23 stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
24 result = append(result, 1)
25 return nil
26 })
27 stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
28 result = append(result, 0)
29 return nil
30 })
31
32 stack.Run(event, config, func() error {
33 result = append(result, 3)
34 return nil
35 })
36
37 if !reflect.DeepEqual(result, []int{0, 1, 2, 3}) {
38 t.Errorf("unexpected middleware order %v", result)
39 }
40 }
41
42 func TestBeforeNotifyReturnErr(t *testing.T) {
43
44 stack := middlewareStack{}
45 err := fmt.Errorf("test")
46 data := []interface{}{errors.New(err, 1)}
47 event, config := newEvent(data, &defaultNotifier)
48
49 stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
50 return err
51 })
52
53 called := false
54
55 e := stack.Run(event, config, func() error {
56 called = true
57 return nil
58 })
59
60 if e != err {
61 t.Errorf("Middleware didn't return the error")
62 }
63
64 if called == true {
65 t.Errorf("Notify was called when BeforeNotify returned False")
66 }
67 }
68
69 func TestBeforeNotifyPanic(t *testing.T) {
70
71 stack := middlewareStack{}
72 err := fmt.Errorf("test")
73 event, _ := newEvent([]interface{}{errors.New(err, 1)}, &defaultNotifier)
74
75 stack.OnBeforeNotify(func(e *Event, c *Configuration) error {
76 panic("oops")
77 })
78
79 called := false
80 b := &bytes.Buffer{}
81
82 stack.Run(event, &Configuration{Logger: log.New(b, log.Prefix(), 0)}, func() error {
83 called = true
84 return nil
85 })
86
87 logged := b.String()
88
89 if logged != "bugsnag/middleware: unexpected panic: oops\n" {
90 t.Errorf("Logged: %s", logged)
91 }
92
93 if called == false {
94 t.Errorf("Notify was not called when BeforeNotify panicked")
95 }
96 }
0 package bugsnag
1
2 import (
3 "github.com/bugsnag/bugsnag-go/v2/errors"
4 )
5
6 var publisher reportPublisher = new(defaultReportPublisher)
7
8 // Notifier sends errors to Bugsnag.
9 type Notifier struct {
10 Config *Configuration
11 RawData []interface{}
12 }
13
14 // New creates a new notifier.
15 // You can pass an instance of bugsnag.Configuration in rawData to change the configuration.
16 // Other values of rawData will be passed to Notify.
17 func New(rawData ...interface{}) *Notifier {
18 config := Config.clone()
19 for i, datum := range rawData {
20 if c, ok := datum.(Configuration); ok {
21 config.update(&c)
22 rawData[i] = nil
23 }
24 }
25
26 return &Notifier{
27 Config: config,
28 RawData: rawData,
29 }
30 }
31
32 // FlushSessionsOnRepanic takes a boolean that indicates whether sessions
33 // should be flushed when AutoNotify repanics. In the case of a fatal panic the
34 // sessions might not get sent to Bugsnag before the application shuts down.
35 // Many frameworks will have their own error handler, and for these frameworks
36 // there is no need to flush sessions as the application will survive the panic
37 // and the sessions can be sent off later. The default value is true, so this
38 // needs only be called if you wish to inform Bugsnag that there is an error
39 // handler that will take care of panics that AutoNotify will re-raise.
40 func (notifier *Notifier) FlushSessionsOnRepanic(shouldFlush bool) {
41 notifier.Config.flushSessionsOnRepanic = shouldFlush
42 }
43
44 // Notify sends an error to Bugsnag. Any rawData you pass here will be sent to
45 // Bugsnag after being converted to JSON. e.g. bugsnag.SeverityError, bugsnag.Context,
46 // or bugsnag.MetaData. Any bools in rawData overrides the
47 // notifier.Config.Synchronous flag.
48 func (notifier *Notifier) Notify(err error, rawData ...interface{}) (e error) {
49 if e := checkForEmptyError(err); e != nil {
50 return e
51 }
52 // Stripping one stackframe to not include this function in the stacktrace
53 // for a manual notification.
54 skipFrames := 1
55 return notifier.NotifySync(errors.New(err, skipFrames), notifier.Config.Synchronous, rawData...)
56 }
57
58 // NotifySync sends an error to Bugsnag. A boolean parameter specifies whether
59 // to send the report in the current context (by default false, i.e.
60 // asynchronous). Any other rawData you pass here will be sent to Bugsnag after
61 // being converted to JSON. E.g. bugsnag.SeverityError, bugsnag.Context, or
62 // bugsnag.MetaData.
63 func (notifier *Notifier) NotifySync(err error, sync bool, rawData ...interface{}) error {
64 if e := checkForEmptyError(err); e != nil {
65 return e
66 }
67 // Stripping one stackframe to not include this function in the stacktrace
68 // for a manual notification.
69 skipFrames := 1
70 event, config := newEvent(append(rawData, errors.New(err, skipFrames), sync), notifier)
71
72 // Never block, start throwing away errors if we have too many.
73 e := middleware.Run(event, config, func() error {
74 return publisher.publishReport(&payload{event, config})
75 })
76
77 if e != nil {
78 config.logf("bugsnag.Notify: %v", e)
79 }
80 return e
81 }
82
83 // AutoNotify notifies Bugsnag of any panics, then repanics.
84 // It sends along any rawData that gets passed in.
85 // Usage:
86 // go func() {
87 // defer AutoNotify()
88 // // (possibly crashy code)
89 // }()
90 func (notifier *Notifier) AutoNotify(rawData ...interface{}) {
91 if err := recover(); err != nil {
92 severity := notifier.getDefaultSeverity(rawData, SeverityError)
93 state := HandledState{SeverityReasonHandledPanic, severity, true, ""}
94 rawData = notifier.appendStateIfNeeded(rawData, state)
95 // We strip the following stackframes as they don't add much
96 // information but would mess with the grouping algorithm
97 // { "file": "github.com/bugsnag/bugsnag-go/notifier.go", "lineNumber": 116, "method": "(*Notifier).AutoNotify" },
98 // { "file": "runtime/asm_amd64.s", "lineNumber": 573, "method": "call32" },
99 skipFrames := 2
100 notifier.NotifySync(errors.New(err, skipFrames), true, rawData...)
101 panic(err)
102 }
103 }
104
105 // Recover logs any panics, then recovers.
106 // It sends along any rawData that gets passed in.
107 // Usage: defer Recover()
108 func (notifier *Notifier) Recover(rawData ...interface{}) {
109 if err := recover(); err != nil {
110 severity := notifier.getDefaultSeverity(rawData, SeverityWarning)
111 state := HandledState{SeverityReasonHandledPanic, severity, false, ""}
112 rawData = notifier.appendStateIfNeeded(rawData, state)
113 notifier.Notify(errors.New(err, 2), rawData...)
114 }
115 }
116
117 func (notifier *Notifier) dontPanic() {
118 if err := recover(); err != nil {
119 notifier.Config.logf("bugsnag/notifier.Notify: panic! %s", err)
120 }
121 }
122
123 // Get defined severity from raw data or a fallback value
124 func (notifier *Notifier) getDefaultSeverity(rawData []interface{}, s severity) severity {
125 allData := append(notifier.RawData, rawData...)
126 for _, datum := range allData {
127 if _, ok := datum.(severity); ok {
128 return datum.(severity)
129 }
130 }
131
132 for _, datum := range allData {
133 if _, ok := datum.(HandledState); ok {
134 return datum.(HandledState).OriginalSeverity
135 }
136 }
137
138 return s
139 }
140
141 func (notifier *Notifier) appendStateIfNeeded(rawData []interface{}, h HandledState) []interface{} {
142
143 for _, datum := range append(notifier.RawData, rawData...) {
144 if _, ok := datum.(HandledState); ok {
145 return rawData
146 }
147 }
148
149 return append(rawData, h)
150 }
0 package bugsnag_test
1
2 import (
3 "fmt"
4 "strings"
5 "testing"
6
7 simplejson "github.com/bitly/go-simplejson"
8 "github.com/bugsnag/bugsnag-go/v2"
9 "github.com/bugsnag/bugsnag-go/v2/errors"
10 . "github.com/bugsnag/bugsnag-go/v2/testutil"
11 )
12
13 var bugsnaggedReports chan []byte
14
15 func notifierSetup(url string) *bugsnag.Notifier {
16 return bugsnag.New(bugsnag.Configuration{
17 APIKey: TestAPIKey,
18 Endpoints: bugsnag.Endpoints{Notify: url, Sessions: url + "/sessions"},
19 })
20 }
21
22 func crash(s interface{}) int {
23 return s.(int)
24 }
25
26 func TestStackframesAreSkippedCorrectly(t *testing.T) {
27 ts, reports := Setup()
28 bugsnaggedReports = reports
29 defer ts.Close()
30 notifier := notifierSetup(ts.URL)
31
32 bugsnag.Configure(bugsnag.Configuration{
33 APIKey: TestAPIKey,
34 Endpoints: bugsnag.Endpoints{Notify: ts.URL, Sessions: ts.URL + "/sessions"},
35 })
36
37 t.Run("notifier.Notify", func(st *testing.T) {
38 notifier.Notify(fmt.Errorf("oopsie"))
39 assertStackframesMatch(t, []errors.StackFrame{
40 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func1", File: "notifier_test.go"},
41 })
42 })
43 t.Run("bugsnag.Notify", func(st *testing.T) {
44 bugsnag.Notify(fmt.Errorf("oopsie"))
45 assertStackframesMatch(t, []errors.StackFrame{
46 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func2", File: "notifier_test.go"},
47 })
48 })
49
50 t.Run("notifier.NotifySync", func(st *testing.T) {
51 notifier.NotifySync(fmt.Errorf("oopsie"), true)
52 assertStackframesMatch(t, []errors.StackFrame{
53 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func3", File: "notifier_test.go"},
54 })
55 })
56
57 t.Run("notifier.AutoNotify", func(st *testing.T) {
58 func() {
59 defer func() { recover() }()
60 defer notifier.AutoNotify()
61 crash("NaN")
62 }()
63 assertStackframesMatch(t, []errors.StackFrame{
64 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func4.1", File: "notifier_test.go"},
65 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func4", File: "notifier_test.go"},
66 })
67 })
68 t.Run("bugsnag.AutoNotify", func(st *testing.T) {
69 func() {
70 defer func() { recover() }()
71 defer bugsnag.AutoNotify()
72 crash("NaN")
73 }()
74 assertStackframesMatch(t, []errors.StackFrame{
75 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func5.1", File: "notifier_test.go"},
76 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func5", File: "notifier_test.go"},
77 })
78 })
79
80 // Expect the following frames to be present for *.Recover
81 /*
82 { "file": "runtime/panic.go", "method": "gopanic" },
83 { "file": "runtime/iface.go", "method": "panicdottypeE" },
84 { "file": "$GOPATH/src/github.com/bugsnag/bugsnag-go/notifier_test.go", "method": "TestStackframesAreSkippedCorrectly.func4.1" },
85 { "file": "$GOPATH/src/github.com/bugsnag/bugsnag-go/notifier_test.go", "method": "TestStackframesAreSkippedCorrectly.func4" },
86 { "file": "testing/testing.go", "method": "tRunner" },
87 { "file": "runtime/asm_amd64.s", "method": "goexit" }
88 */
89 t.Run("notifier.Recover", func(st *testing.T) {
90 func() {
91 defer notifier.Recover()
92 crash("NaN")
93 }()
94 assertStackframesMatch(t, []errors.StackFrame{
95 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func6.1", File: "notifier_test.go"},
96 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func6", File: "notifier_test.go"},
97 })
98 })
99 t.Run("bugsnag.Recover", func(st *testing.T) {
100 func() {
101 defer bugsnag.Recover()
102 crash("NaN")
103 }()
104 assertStackframesMatch(t, []errors.StackFrame{
105 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func7.1", File: "notifier_test.go"},
106 errors.StackFrame{Name: "TestStackframesAreSkippedCorrectly.func7", File: "notifier_test.go"},
107 })
108 })
109 }
110
111 func TestModifyingEventsWithCallbacks(t *testing.T) {
112 server, eventQueue := Setup()
113 defer server.Close()
114 notifier := notifierSetup(server.URL)
115
116 bugsnag.Configure(bugsnag.Configuration{
117 APIKey: TestAPIKey,
118 Endpoints: bugsnag.Endpoints{Notify: server.URL, Sessions: server.URL + "/sessions"},
119 })
120
121 t.Run("bugsnag.Notify change unhandled in block", func(st *testing.T) {
122 notifier.Notify(fmt.Errorf("ahoy"), func(event *bugsnag.Event) {
123 event.Unhandled = true
124 })
125 json, _ := simplejson.NewJson(<-eventQueue)
126 event := GetIndex(json, "events", 0)
127 exception := GetIndex(event, "exceptions", 0)
128 message := exception.Get("message").MustString()
129 unhandled := event.Get("unhandled").MustBool()
130 overridden := event.Get("severityReason").Get("unhandledOverridden").MustBool()
131 if message != "ahoy" {
132 st.Errorf("incorrect error message '%s'", message)
133 }
134 if !unhandled {
135 st.Errorf("failed to change handled-ness in block")
136 }
137 if !overridden {
138 st.Errorf("failed to set handledness change in block")
139 }
140 })
141
142 t.Run("bugsnag.Notify with block", func(st *testing.T) {
143 notifier.Notify(fmt.Errorf("bnuuy"), bugsnag.Context{String: "should be overridden"}, func(event *bugsnag.Event) {
144 event.Context = "known unknowns"
145 })
146 json, _ := simplejson.NewJson(<-eventQueue)
147 event := GetIndex(json, "events", 0)
148 context := event.Get("context").MustString()
149 exception := GetIndex(event, "exceptions", 0)
150 class := exception.Get("errorClass").MustString()
151 message := exception.Get("message").MustString()
152 if class != "*errors.errorString" {
153 st.Errorf("incorrect error class '%s'", class)
154 }
155 if message != "bnuuy" {
156 st.Errorf("incorrect error message '%s'", message)
157 }
158 if context != "known unknowns" {
159 st.Errorf("failed to change context in block. '%s'", context)
160 }
161 if event.Get("unhandled").MustBool() {
162 st.Errorf("error is unexpectedly unhandled")
163 }
164 if overridden, err := event.Get("severityReason").Get("unhandledOverridden").Bool(); err == nil {
165 // if err == nil, then the value existed in the payload. the expectation
166 // is that unhandledOverridden is not sent when handled-ness is not changed.
167 st.Errorf("error unexpectedly has unhandledOverridden: %v", overridden)
168 }
169 })
170 }
171
172 func assertStackframesMatch(t *testing.T, expected []errors.StackFrame) {
173 var lastmatch int = 0
174 var matched int = 0
175 event, _ := simplejson.NewJson(<-bugsnaggedReports)
176 json := GetIndex(event, "events", 0)
177 stacktrace := GetIndex(json, "exceptions", 0).Get("stacktrace")
178 for i := 0; i < len(stacktrace.MustArray()); i++ {
179 actualFrame := stacktrace.GetIndex(i)
180 file := actualFrame.Get("file").MustString()
181 method := actualFrame.Get("method").MustString()
182 for index, expectedFrame := range expected {
183 if index < lastmatch {
184 continue
185 }
186 if strings.HasSuffix(file, expectedFrame.File) && expectedFrame.Name == method {
187 lastmatch = index
188 matched++
189 }
190 }
191 }
192
193 if matched != len(expected) {
194 s, _ := stacktrace.EncodePretty()
195 t.Errorf("failed to find matches for %d frames: '%v'\ngot: '%v'", len(expected)-matched, expected[matched:], string(s))
196 }
197 }
0 package bugsnag
1
2 import (
3 "github.com/bugsnag/bugsnag-go/v2/errors"
4 "github.com/bugsnag/bugsnag-go/v2/sessions"
5 "github.com/bugsnag/panicwrap"
6 )
7
8 // Forks and re-runs your program to add panic monitoring. This function does
9 // not return on one process, instead listening on stderr of the other process,
10 // which returns nil.
11 //
12 // Related: https://godoc.org/github.com/bugsnag/panicwrap#BasicMonitor
13 func defaultPanicHandler() {
14 defer defaultNotifier.dontPanic()
15 ctx := sessions.SendStartupSession(&sessionTrackingConfig)
16
17 err := panicwrap.BasicMonitor(func(output string) {
18 toNotify, err := errors.ParsePanic(output)
19
20 if err != nil {
21 defaultNotifier.Config.logf("bugsnag.handleUncaughtPanic: %v", err)
22 }
23 state := HandledState{SeverityReasonUnhandledPanic, SeverityError, true, ""}
24 defaultNotifier.NotifySync(toNotify, true, state, ctx)
25
26 })
27
28 if err != nil {
29 defaultNotifier.Config.logf("bugsnag.handleUncaughtPanic: %v", err)
30 }
31 }
0 package bugsnag
1
2 import (
3 "context"
4 "os"
5 "os/exec"
6 "runtime"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/bitly/go-simplejson"
12 "github.com/bugsnag/bugsnag-go/v2/sessions"
13 )
14
15 // Test the panic handler by launching a new process which runs the init()
16 // method in this file and causing a handled panic
17 func TestPanicHandlerHandledPanic(t *testing.T) {
18 ts, reports := setup()
19 defer ts.Close()
20
21 startPanickingProcess(t, "handled", ts.URL)
22
23 json, err := simplejson.NewJson(<-reports)
24 if err != nil {
25 t.Fatal(err)
26 }
27
28 assertPayload(t, json, eventJSON{
29 App: &appJSON{},
30 Context: "",
31 Device: &deviceJSON{Hostname: "web1"},
32 GroupingHash: "",
33 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
34 Severity: "error",
35 SeverityReason: &severityReasonJSON{Type: SeverityReasonHandledPanic},
36 Unhandled: true,
37 Request: &RequestJSON{},
38 User: &User{},
39 Exceptions: []exceptionJSON{{ErrorClass: "*errors.errorString", Message: "ruh roh"}},
40 })
41
42 event := getIndex(json, "events", 0)
43 assertValidSession(t, event, true)
44
45 stacktrace := getIndex(event, "exceptions", 0).Get("stacktrace")
46 found := false
47 for i := 0; i < len(stacktrace.MustArray()); i++ {
48 frame := stacktrace.GetIndex(i)
49 if strings.HasSuffix(getString(frame, "file"), "panicwrap_test.go") && getInt(frame, "lineNumber") != 0 {
50 found = true
51 break
52 }
53 }
54 if !found {
55 s, _ := stacktrace.EncodePretty()
56 t.Errorf("no stack frame found matching this file in stack trace: %v", string(s))
57 }
58 }
59
60 // Test the panic handler by launching a new process which runs the init()
61 // method in this file and causing an unhandled panic
62 func TestPanicHandlerUnhandledPanic(t *testing.T) {
63 if runtime.GOOS == "windows" {
64 t.Skip("not compatible with windows builds")
65 return
66 }
67 ts, reports := setup()
68 defer ts.Close()
69
70 startPanickingProcess(t, "unhandled", ts.URL)
71 json, err := simplejson.NewJson(<-reports)
72 if err != nil {
73 t.Fatal(err)
74 }
75 assertPayload(t, json, eventJSON{
76 App: &appJSON{},
77 Context: "",
78 Device: &deviceJSON{Hostname: "web1"},
79 GroupingHash: "",
80 Session: &sessionJSON{Events: sessions.EventCounts{Handled: 0, Unhandled: 1}},
81 Severity: "error",
82 SeverityReason: &severityReasonJSON{Type: SeverityReasonUnhandledPanic},
83 Unhandled: true,
84 Request: &RequestJSON{},
85 User: &User{},
86 Exceptions: []exceptionJSON{{ErrorClass: "panic", Message: "ruh roh"}},
87 })
88 }
89
90 func startPanickingProcess(t *testing.T, variant string, endpoint string) {
91 exePath, err := os.Executable()
92 if err != nil {
93 t.Fatal(err)
94 }
95
96 // Use the same trick as panicwrap() to re-run ourselves.
97 // In the init() block below, we will then panic.
98 cmd := exec.Command(exePath, os.Args[1:]...)
99 cmd.Env = append(os.Environ(), "BUGSNAG_API_KEY="+testAPIKey, "BUGSNAG_NOTIFY_ENDPOINT="+endpoint, "please_panic="+variant)
100
101 // Gift for the debugging developer:
102 // As these tests shell out we don't see, or even want to see, the output
103 // of these tests by default. The following two lines may be uncommented
104 // in order to see what this command would print to stdout and stderr.
105 /*
106 bytes, _ := cmd.CombinedOutput()
107 fmt.Println(string(bytes))
108 */
109
110 if err = cmd.Start(); err != nil {
111 t.Fatal(err)
112 }
113
114 if err = cmd.Wait(); err.Error() != "exit status 2" {
115 t.Fatal(err)
116 }
117 }
118
119 func init() {
120 if os.Getenv("please_panic") == "handled" {
121 Configure(Configuration{
122 APIKey: os.Getenv("BUGSNAG_API_KEY"),
123 Endpoints: Endpoints{Notify: os.Getenv("BUGSNAG_NOTIFY_ENDPOINT")},
124 Hostname: "web1",
125 ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"}})
126 go func() {
127 ctx := StartSession(context.Background())
128 defer AutoNotify(ctx)
129
130 panick()
131 }()
132 // Plenty of time to crash, it shouldn't need any of it.
133 time.Sleep(1 * time.Second)
134 } else if os.Getenv("please_panic") == "unhandled" {
135 Configure(Configuration{
136 APIKey: os.Getenv("BUGSNAG_API_KEY"),
137 Endpoints: Endpoints{Notify: os.Getenv("BUGSNAG_NOTIFY_ENDPOINT")},
138 Hostname: "web1",
139 Synchronous: true,
140 ProjectPackages: []string{"github.com/bugsnag/bugsnag-go"}})
141 panick()
142 }
143 }
144
145 func panick() {
146 panic("ruh roh")
147 }
0 package bugsnag
1
2 import (
3 "bytes"
4 "encoding/json"
5 "fmt"
6 "net/http"
7 "runtime"
8 "sync"
9 "time"
10
11 "github.com/bugsnag/bugsnag-go/v2/device"
12 "github.com/bugsnag/bugsnag-go/v2/headers"
13 "github.com/bugsnag/bugsnag-go/v2/sessions"
14 )
15
16 const notifyPayloadVersion = "4"
17
18 var sessionMutex sync.Mutex
19
20 type payload struct {
21 *Event
22 *Configuration
23 }
24
25 type hash map[string]interface{}
26
27 func (p *payload) deliver() error {
28
29 if len(p.APIKey) != 32 {
30 return fmt.Errorf("bugsnag/payload.deliver: invalid api key: '%s'", p.APIKey)
31 }
32
33 buf, err := p.MarshalJSON()
34
35 if err != nil {
36 return fmt.Errorf("bugsnag/payload.deliver: %v", err)
37 }
38
39 client := http.Client{
40 Transport: p.Transport,
41 }
42 req, err := http.NewRequest("POST", p.Endpoints.Notify, bytes.NewBuffer(buf))
43 if err != nil {
44 return fmt.Errorf("bugsnag/payload.deliver unable to create request: %v", err)
45 }
46 for k, v := range headers.PrefixedHeaders(p.APIKey, notifyPayloadVersion) {
47 req.Header.Add(k, v)
48 }
49 resp, err := client.Do(req)
50 if err != nil {
51 return fmt.Errorf("bugsnag/payload.deliver: %v", err)
52 }
53 defer resp.Body.Close()
54
55 if resp.StatusCode != 200 {
56 return fmt.Errorf("bugsnag/payload.deliver: Got HTTP %s", resp.Status)
57 }
58
59 return nil
60 }
61
62 func (p *payload) MarshalJSON() ([]byte, error) {
63 return json.Marshal(reportJSON{
64 APIKey: p.APIKey,
65 Events: []eventJSON{
66 eventJSON{
67 App: &appJSON{
68 ReleaseStage: p.ReleaseStage,
69 Type: p.AppType,
70 Version: p.AppVersion,
71 },
72 Context: p.Context,
73 Device: &deviceJSON{
74 Hostname: p.Hostname,
75 OsName: runtime.GOOS,
76 RuntimeVersions: device.GetRuntimeVersions(),
77 },
78 Request: p.Request,
79 Exceptions: p.exceptions(),
80 GroupingHash: p.GroupingHash,
81 Metadata: p.MetaData.sanitize(p.ParamsFilters),
82 PayloadVersion: notifyPayloadVersion,
83 Session: p.makeSession(),
84 Severity: p.Severity.String,
85 SeverityReason: p.severityReasonPayload(),
86 Unhandled: p.Unhandled,
87 User: p.User,
88 },
89 },
90 Notifier: notifierJSON{
91 Name: "Bugsnag Go",
92 URL: "https://github.com/bugsnag/bugsnag-go",
93 Version: Version,
94 },
95 })
96 }
97
98 func (p *payload) makeSession() *sessionJSON {
99 // If a context has not been applied to the payload then assume that no
100 // session has started either
101 if p.Ctx == nil {
102 return nil
103 }
104
105 sessionMutex.Lock()
106 defer sessionMutex.Unlock()
107 session := sessions.IncrementEventCountAndGetSession(p.Ctx, p.Unhandled)
108 if session != nil {
109 s := *session
110 return &sessionJSON{
111 ID: s.ID,
112 StartedAt: s.StartedAt.UTC().Format(time.RFC3339),
113 Events: sessions.EventCounts{
114 Handled: s.EventCounts.Handled,
115 Unhandled: s.EventCounts.Unhandled,
116 },
117 }
118 }
119 return nil
120 }
121
122 func (p *payload) severityReasonPayload() *severityReasonJSON {
123 if reason := p.handledState.SeverityReason; reason != "" {
124 json := &severityReasonJSON{
125 Type: reason,
126 UnhandledOverridden: p.handledState.Unhandled != p.Unhandled,
127 }
128 if p.handledState.Framework != "" {
129 json.Attributes = make(map[string]string, 1)
130 json.Attributes["framework"] = p.handledState.Framework
131 }
132 return json
133 }
134 return nil
135 }
136
137 func (p *payload) exceptions() []exceptionJSON {
138 exceptions := []exceptionJSON{
139 exceptionJSON{
140 ErrorClass: p.ErrorClass,
141 Message: p.Message,
142 Stacktrace: p.Stacktrace,
143 },
144 }
145
146 if p.Error == nil {
147 return exceptions
148 }
149
150 cause := p.Error.Cause
151 for cause != nil {
152 exceptions = append(exceptions, exceptionJSON{
153 ErrorClass: cause.TypeName(),
154 Message: cause.Error(),
155 Stacktrace: generateStacktrace(cause, p.Configuration),
156 })
157 cause = cause.Cause
158 }
159
160 return exceptions
161 }
0 package bugsnag
1
2 import (
3 "context"
4 "fmt"
5 "runtime"
6 "strings"
7 "testing"
8
9 "github.com/bugsnag/bugsnag-go/v2/errors"
10 "github.com/bugsnag/bugsnag-go/v2/sessions"
11 )
12
13 const expSmall = `{"apiKey":"","events":[{"app":{"releaseStage":""},"device":{"osName":"%s","runtimeVersions":{"go":"%s"}},"exceptions":[{"errorClass":"","message":"","stacktrace":null}],"metaData":{},"payloadVersion":"4","severity":"","unhandled":false}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"` + Version + `"}}`
14
15 // The large payload has a timestamp in it which makes it awkward to assert against.
16 // Instead, assert that the timestamp property exist, along with the rest of the expected payload
17 const expLargePre = `{"apiKey":"166f5ad3590596f9aa8d601ea89af845","events":[{"app":{"releaseStage":"mega-production","type":"gin","version":"1.5.3"},"context":"/api/v2/albums","device":{"hostname":"super.duper.site","osName":"%s","runtimeVersions":{"go":"%s"}},"exceptions":[{"errorClass":"error class","message":"error message goes here","stacktrace":[{"method":"doA","file":"a.go","lineNumber":65},{"method":"fetchB","file":"b.go","lineNumber":99,"inProject":true},{"method":"incrementI","file":"i.go","lineNumber":651}]}],"groupingHash":"custom grouping hash","metaData":{"custom tab":{"my key":"my value"}},"payloadVersion":"4","session":{"startedAt":"`
18 const expLargePost = `,"severity":"info","severityReason":{"type":"unhandledError","attributes":{"framework":"gin"}},"unhandled":true,"user":{"id":"1234baerg134","name":"Kool Kidz on da bus","email":"typo@busgang.com"}}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"` + Version + `"}}`
19
20 func TestMarshalEmptyPayload(t *testing.T) {
21 sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
22 p := payload{&Event{Ctx: context.Background()}, &Configuration{}}
23 bytes, _ := p.MarshalJSON()
24 exp := fmt.Sprintf(expSmall, runtime.GOOS, runtime.Version())
25 if got := string(bytes[:]); got != exp {
26 t.Errorf("Payload different to what was expected. \nGot: %s\nExp: %s", got, exp)
27 }
28 }
29
30 func TestMarshalLargePayload(t *testing.T) {
31 payload := makeLargePayload()
32 bytes, _ := payload.MarshalJSON()
33 got := string(bytes[:])
34 expPre := fmt.Sprintf(expLargePre, runtime.GOOS, runtime.Version())
35 if !strings.Contains(got, expPre) {
36 t.Errorf("Expected large payload to contain\n'%s'\n but was\n'%s'", expPre, got)
37
38 }
39 if !strings.Contains(got, expLargePost) {
40 t.Errorf("Expected large payload to contain\n'%s'\n but was\n'%s'", expLargePost, got)
41 }
42 }
43
44 func makeLargePayload() *payload {
45 stackframes := []StackFrame{
46 {Method: "doA", File: "a.go", LineNumber: 65, InProject: false},
47 {Method: "fetchB", File: "b.go", LineNumber: 99, InProject: true},
48 {Method: "incrementI", File: "i.go", LineNumber: 651, InProject: false},
49 }
50 user := User{
51 Id: "1234baerg134",
52 Name: "Kool Kidz on da bus",
53 Email: "typo@busgang.com",
54 }
55 handledState := HandledState{
56 SeverityReason: SeverityReasonUnhandledError,
57 OriginalSeverity: severity{String: "error"},
58 Unhandled: true,
59 Framework: "gin",
60 }
61
62 ctx := context.Background()
63 ctx = StartSession(ctx)
64
65 event := Event{
66 Error: &errors.Error{},
67 RawData: nil,
68 ErrorClass: "error class",
69 Message: "error message goes here",
70 Stacktrace: stackframes,
71 Context: "/api/v2/albums",
72 Severity: SeverityInfo,
73 GroupingHash: "custom grouping hash",
74 User: &user,
75 Ctx: ctx,
76 MetaData: map[string]map[string]interface{}{
77 "custom tab": map[string]interface{}{
78 "my key": "my value",
79 },
80 },
81 Unhandled: true,
82 handledState: handledState,
83 }
84 config := Configuration{
85 APIKey: testAPIKey,
86 ReleaseStage: "mega-production",
87 AppType: "gin",
88 AppVersion: "1.5.3",
89 Hostname: "super.duper.site",
90 }
91 return &payload{&event, &config}
92 }
0 package bugsnag
1
2 import (
3 "github.com/bugsnag/bugsnag-go/v2/device"
4 "github.com/bugsnag/bugsnag-go/v2/sessions"
5 uuid "github.com/gofrs/uuid"
6 )
7
8 type reportJSON struct {
9 APIKey string `json:"apiKey"`
10 Events []eventJSON `json:"events"`
11 Notifier notifierJSON `json:"notifier"`
12 }
13
14 type notifierJSON struct {
15 Name string `json:"name"`
16 URL string `json:"url"`
17 Version string `json:"version"`
18 }
19
20 type eventJSON struct {
21 App *appJSON `json:"app"`
22 Context string `json:"context,omitempty"`
23 Device *deviceJSON `json:"device,omitempty"`
24 Request *RequestJSON `json:"request,omitempty"`
25 Exceptions []exceptionJSON `json:"exceptions"`
26 GroupingHash string `json:"groupingHash,omitempty"`
27 Metadata interface{} `json:"metaData"`
28 PayloadVersion string `json:"payloadVersion"`
29 Session *sessionJSON `json:"session,omitempty"`
30 Severity string `json:"severity"`
31 SeverityReason *severityReasonJSON `json:"severityReason,omitempty"`
32 Unhandled bool `json:"unhandled"`
33 User *User `json:"user,omitempty"`
34 }
35
36 type sessionJSON struct {
37 StartedAt string `json:"startedAt"`
38 ID uuid.UUID `json:"id"`
39 Events sessions.EventCounts `json:"events"`
40 }
41
42 type appJSON struct {
43 ReleaseStage string `json:"releaseStage"`
44 Type string `json:"type,omitempty"`
45 Version string `json:"version,omitempty"`
46 }
47
48 type exceptionJSON struct {
49 ErrorClass string `json:"errorClass"`
50 Message string `json:"message"`
51 Stacktrace []StackFrame `json:"stacktrace"`
52 }
53
54 type severityReasonJSON struct {
55 Type SeverityReason `json:"type,omitempty"`
56 Attributes map[string]string `json:"attributes,omitempty"`
57 UnhandledOverridden bool `json:"unhandledOverridden,omitempty"`
58 }
59
60 type deviceJSON struct {
61 Hostname string `json:"hostname,omitempty"`
62 OsName string `json:"osName,omitempty"`
63
64 RuntimeVersions *device.RuntimeVersions `json:"runtimeVersions,omitempty"`
65 }
66
67 // RequestJSON is the request information that populates the Request tab in the dashboard.
68 type RequestJSON struct {
69 ClientIP string `json:"clientIp,omitempty"`
70 Headers map[string]string `json:"headers,omitempty"`
71 HTTPMethod string `json:"httpMethod,omitempty"`
72 URL string `json:"url,omitempty"`
73 Referer string `json:"referer,omitempty"`
74 }
0 package bugsnag
1
2 import "fmt"
3
4 type reportPublisher interface {
5 publishReport(*payload) error
6 }
7
8 type defaultReportPublisher struct{}
9
10 func (*defaultReportPublisher) publishReport(p *payload) error {
11 p.logf("notifying bugsnag: %s", p.Message)
12 if !p.notifyInReleaseStage() {
13 return fmt.Errorf("not notifying in %s", p.ReleaseStage)
14 }
15 if p.Synchronous {
16 return p.deliver()
17 }
18
19 go func(p *payload) {
20 if err := p.deliver(); err != nil {
21 // Ensure that any errors are logged if they occur in a goroutine.
22 p.logf("bugsnag/defaultReportPublisher.publishReport: %v", err)
23 }
24 }(p)
25 return nil
26 }
0 package bugsnag
1
2 import (
3 "context"
4 "net/http"
5 "net/url"
6 "strings"
7 )
8
9 const requestContextKey requestKey = iota
10
11 type requestKey int
12
13 // AttachRequestData returns a child of the given context with the request
14 // object attached for later extraction by the notifier in order to
15 // automatically record request data
16 func AttachRequestData(ctx context.Context, r *http.Request) context.Context {
17 return context.WithValue(ctx, requestContextKey, r)
18 }
19
20 // extractRequestInfo looks for the request object that the notifier
21 // automatically attaches to the context when using any of the supported
22 // frameworks or bugsnag.HandlerFunc or bugsnag.Handler, and returns sub-object
23 // supported by the notify API.
24 func extractRequestInfo(ctx context.Context) (*RequestJSON, *http.Request) {
25 if req := getRequestIfPresent(ctx); req != nil {
26 return extractRequestInfoFromReq(req), req
27 }
28 return nil, nil
29 }
30
31 // extractRequestInfoFromReq extracts the request information the notify API
32 // understands from the given HTTP request. Returns the sub-object supported by
33 // the notify API.
34 func extractRequestInfoFromReq(req *http.Request) *RequestJSON {
35 return &RequestJSON{
36 ClientIP: req.RemoteAddr,
37 HTTPMethod: req.Method,
38 URL: sanitizeURL(req),
39 Referer: req.Referer(),
40 Headers: parseRequestHeaders(req.Header),
41 }
42 }
43
44 // sanitizeURL will build up the URL matching the request. It will filter query parameters to remove sensitive fields.
45 // The query part of the URL might appear differently (different order of parameters) if any filtering was done.
46 func sanitizeURL(req *http.Request) string {
47 scheme := "http"
48 if req.TLS != nil {
49 scheme = "https"
50 }
51
52 rawQuery := req.URL.RawQuery
53 parsedQuery, err := url.ParseQuery(req.URL.RawQuery)
54
55 if err != nil {
56 return scheme + "://" + req.Host + req.RequestURI
57 }
58
59 changed := false
60 for key, values := range parsedQuery {
61 if contains(Config.ParamsFilters, key) {
62 for i := range values {
63 values[i] = "BUGSNAG_URL_FILTERED"
64 changed = true
65 }
66 }
67 }
68
69 if changed {
70 rawQuery = parsedQuery.Encode()
71 rawQuery = strings.Replace(rawQuery, "BUGSNAG_URL_FILTERED", "[FILTERED]", -1)
72 }
73
74 u := url.URL{
75 Scheme: scheme,
76 Host: req.Host,
77 Path: req.URL.Path,
78 RawQuery: rawQuery,
79 }
80 return u.String()
81 }
82
83 func parseRequestHeaders(header map[string][]string) map[string]string {
84 headers := make(map[string]string)
85 for k, v := range header {
86 // Headers can have multiple values, in which case we report them as csv
87 if contains(Config.ParamsFilters, k) {
88 headers[k] = "[FILTERED]"
89 } else {
90 headers[k] = strings.Join(v, ",")
91 }
92 }
93 return headers
94 }
95
96 func contains(slice []string, e string) bool {
97 for _, s := range slice {
98 if strings.Contains(strings.ToLower(e), strings.ToLower(s)) {
99 return true
100 }
101 }
102 return false
103 }
104
105 func getRequestIfPresent(ctx context.Context) *http.Request {
106 if ctx == nil {
107 return nil
108 }
109 val := ctx.Value(requestContextKey)
110 if val == nil {
111 return nil
112 }
113 return val.(*http.Request)
114 }
0 package bugsnag
1
2 import (
3 "context"
4 "net/http"
5 "net/http/httptest"
6 "net/url"
7 "strings"
8 "testing"
9 )
10
11 func TestRequestInformationGetsExtracted(t *testing.T) {
12 contexts := make(chan context.Context, 1)
13 hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14 ctx := r.Context()
15 ctx = AttachRequestData(ctx, r)
16 contexts <- ctx
17 })
18 ts := httptest.NewServer(hf)
19 defer ts.Close()
20 http.Get(ts.URL + "/1234abcd?fish=bird")
21
22 reqJSON, req := extractRequestInfo(<-contexts)
23 if reqJSON.ClientIP == "" {
24 t.Errorf("expected to find an IP address for the request but was blank")
25 }
26 if got, exp := reqJSON.HTTPMethod, "GET"; got != exp {
27 t.Errorf("expected HTTP method to be '%s' but was '%s'", exp, got)
28 }
29 if got, exp := req.URL.Path, "/1234abcd"; got != exp {
30 t.Errorf("expected request URL to be '%s' but was '%s'", exp, got)
31 }
32 if got, exp := reqJSON.URL, "/1234abcd?fish=bird"; !strings.Contains(got, exp) {
33 t.Errorf("expected request URL to contain '%s' but was '%s'", exp, got)
34 }
35 if got, exp := reqJSON.Referer, ""; got != exp {
36 t.Errorf("expected request referer to be '%s' but was '%s'", exp, got)
37 }
38 if got, exp := reqJSON.Headers["Accept-Encoding"], "gzip"; got != exp {
39 t.Errorf("expected Accept-Encoding to be '%s' but was '%s'", exp, got)
40 }
41 if got, exp := reqJSON.Headers["User-Agent"], "Go-http-client"; !strings.Contains(got, exp) {
42 t.Errorf("expected user agent to contain '%s' but was '%s'", exp, got)
43 }
44 }
45
46 func TestRequestExtractorCanHandleAbsentContext(t *testing.T) {
47 if got, _ := extractRequestInfo(nil); got != nil {
48 //really just testing that nothing panics here
49 t.Errorf("expected nil contexts to give nil sub-objects, but was '%s'", got)
50 }
51 if got, _ := extractRequestInfo(context.Background()); got != nil {
52 //really just testing that nothing panics here
53 t.Errorf("expected contexts without requst info to give nil sub-objects, but was '%s'", got)
54 }
55 }
56
57 func TestExtractRequestInfoFromReq_RedactURL(t *testing.T) {
58 testCases := []struct {
59 in url.URL
60 exp string
61 }{
62 {in: url.URL{}, exp: "http://example.com"},
63 {in: url.URL{Path: "/"}, exp: "http://example.com/"},
64 {in: url.URL{Path: "/foo.html"}, exp: "http://example.com/foo.html"},
65 {in: url.URL{Path: "/foo.html", RawQuery: "q=something&bar=123"}, exp: "http://example.com/foo.html?q=something&bar=123"},
66 {in: url.URL{Path: "/foo.html", RawQuery: "foo=1&foo=2&foo=3"}, exp: "http://example.com/foo.html?foo=1&foo=2&foo=3"},
67
68 // Invalid query string.
69 {in: url.URL{Path: "/foo", RawQuery: "%"}, exp: "http://example.com/foo?%"},
70
71 // Query params contain secrets
72 {in: url.URL{Path: "/foo.html", RawQuery: "access_token=something"}, exp: "http://example.com/foo.html?access_token=[FILTERED]"},
73 {in: url.URL{Path: "/foo.html", RawQuery: "access_token=something&access_token=&foo=bar"}, exp: "http://example.com/foo.html?access_token=[FILTERED]&access_token=[FILTERED]&foo=bar"},
74 }
75
76 for _, tc := range testCases {
77 requestURI := tc.in.Path
78 if tc.in.RawQuery != "" {
79 requestURI += "?" + tc.in.RawQuery
80 }
81 req := &http.Request{
82 Host: "example.com",
83 URL: &tc.in,
84 RequestURI: requestURI,
85 }
86 result := extractRequestInfoFromReq(req)
87 if result.URL != tc.exp {
88 t.Errorf("expected URL to be '%s' but was '%s'", tc.exp, result.URL)
89 }
90 }
91 }
92
93 func TestParseHeadersWillSanitiseIllegalParams(t *testing.T) {
94 headers := make(map[string][]string)
95 headers["password"] = []string{"correct horse battery staple"}
96 headers["secret"] = []string{"I am Banksy"}
97 headers["authorization"] = []string{"licence to kill -9"}
98 headers["custom-made-secret"] = []string{"I'm the insider at Sotheby's"}
99 for k, v := range parseRequestHeaders(headers) {
100 if v != "[FILTERED]" {
101 t.Errorf("expected '%s' to be [FILTERED], but was '%s'", k, v)
102 }
103 }
104 }
0 package sessions
1
2 import (
3 "log"
4 "net/http"
5 "sync"
6 "time"
7 )
8
9 // SessionTrackingConfiguration defines the configuration options relevant for session tracking.
10 // These are likely a subset of the global bugsnag.Configuration. Users should
11 // not modify this struct directly but rather call
12 // `bugsnag.Configure(bugsnag.Configuration)` which will update this configuration in return.
13 type SessionTrackingConfiguration struct {
14 // PublishInterval defines how often the sessions are sent off to the session server.
15 PublishInterval time.Duration
16
17 // AutoCaptureSessions can be set to false to disable automatic session
18 // tracking. If you want control over what is deemed a session, you can
19 // switch off automatic session tracking with this configuration, and call
20 // bugsnag.StartSession() when appropriate for your application. See the
21 // official docs for instructions and examples of associating handled
22 // errors with sessions and ensuring error rate accuracy on the Bugsnag
23 // dashboard. This will default to true, but is stored as an interface to enable
24 // us to detect when this option has not been set.
25 AutoCaptureSessions interface{}
26
27 // APIKey defines the API key for the Bugsnag project. Same value as for reporting errors.
28 APIKey string
29 // Endpoint is the URI of the session server to receive session payloads.
30 Endpoint string
31 // Version defines the current version of the notifier.
32 Version string
33
34 // ReleaseStage defines the release stage, e.g. "production" or "staging",
35 // that this session occurred in. The release stage, in combination with
36 // the app version make up the release that Bugsnag tracks.
37 ReleaseStage string
38 // Hostname defines the host of the server this application is running on.
39 Hostname string
40 // AppType defines the type of the application.
41 AppType string
42 // AppVersion defines the version of the application.
43 AppVersion string
44 // Transport defines the http.RoundTripper to be used for managing HTTP requests.
45 Transport http.RoundTripper
46
47 // The release stages to notify about sessions in. If you set this then
48 // bugsnag-go will only send sessions to Bugsnag if the release stage
49 // is listed here.
50 NotifyReleaseStages []string
51
52 // Logger is the logger that Bugsnag should log to. Uses the same defaults
53 // as go's builtin logging package. This logger gets invoked when any error
54 // occurs inside the library itself.
55 Logger interface {
56 Printf(format string, v ...interface{})
57 }
58
59 mutex sync.Mutex
60 }
61
62 // Update modifies the values inside the receiver to match the non-default properties of the given config.
63 // Existing properties will not be cleared when given empty fields.
64 func (c *SessionTrackingConfiguration) Update(config *SessionTrackingConfiguration) {
65 c.mutex.Lock()
66 defer c.mutex.Unlock()
67 if config.PublishInterval != 0 {
68 c.PublishInterval = config.PublishInterval
69 }
70 if config.APIKey != "" {
71 c.APIKey = config.APIKey
72 }
73 if config.Endpoint != "" {
74 c.Endpoint = config.Endpoint
75 }
76 if config.Version != "" {
77 c.Version = config.Version
78 }
79 if config.ReleaseStage != "" {
80 c.ReleaseStage = config.ReleaseStage
81 }
82 if config.Hostname != "" {
83 c.Hostname = config.Hostname
84 }
85 if config.AppType != "" {
86 c.AppType = config.AppType
87 }
88 if config.AppVersion != "" {
89 c.AppVersion = config.AppVersion
90 }
91 if config.Transport != nil {
92 c.Transport = config.Transport
93 }
94 if config.Logger != nil {
95 c.Logger = config.Logger
96 }
97 if config.NotifyReleaseStages != nil {
98 c.NotifyReleaseStages = config.NotifyReleaseStages
99 }
100 if config.AutoCaptureSessions != nil {
101 c.AutoCaptureSessions = config.AutoCaptureSessions
102 }
103 }
104
105 func (c *SessionTrackingConfiguration) logf(fmt string, args ...interface{}) {
106 if c != nil && c.Logger != nil {
107 c.Logger.Printf(fmt, args...)
108 } else {
109 log.Printf(fmt, args...)
110 }
111 }
112
113 // IsAutoCaptureSessions identifies whether or not the notifier should
114 // automatically capture sessions as requests come in. It's a convenience
115 // wrapper that allows automatic session capturing to be enabled by default.
116 func (c *SessionTrackingConfiguration) IsAutoCaptureSessions() bool {
117 if c.AutoCaptureSessions == nil {
118 return true // enabled by default
119 }
120 if val, ok := c.AutoCaptureSessions.(bool); ok {
121 return val
122 }
123 // It has been configured to *something* (although not a valid value)
124 // assume the user wanted to disable this option.
125 return false
126 }
0 package sessions
1
2 import (
3 "net/http"
4 "reflect"
5 "testing"
6 "time"
7 )
8
9 func TestConfigDoesNotChangeGivenBlankValues(t *testing.T) {
10 c := testConfig()
11 exp := testConfig()
12 c.Update(&SessionTrackingConfiguration{})
13 tt := []struct {
14 name string
15 expected interface{}
16 got interface{}
17 }{
18 {"PublishInterval", exp.PublishInterval, c.PublishInterval},
19 {"APIKey", exp.APIKey, c.APIKey},
20 {"Endpoint", exp.Endpoint, c.Endpoint},
21 {"Version", exp.Version, c.Version},
22 {"ReleaseStage", exp.ReleaseStage, c.ReleaseStage},
23 {"Hostname", exp.Hostname, c.Hostname},
24 {"AppType", exp.AppType, c.AppType},
25 {"AppVersion", exp.AppVersion, c.AppVersion},
26 {"Transport", exp.Transport, c.Transport},
27 {"NotifyReleaseStages", exp.NotifyReleaseStages, c.NotifyReleaseStages},
28 }
29 for _, tc := range tt {
30 if !reflect.DeepEqual(tc.got, tc.expected) {
31 t.Errorf("Expected '%s' to be '%v' but was '%v'", tc.name, tc.expected, tc.got)
32 }
33 }
34 }
35
36 func TestConfigUpdatesGivenNonDefaultValues(t *testing.T) {
37 c := testConfig()
38 exp := SessionTrackingConfiguration{
39 PublishInterval: 40 * time.Second,
40 APIKey: "api234",
41 Endpoint: "https://docs.bugsnag.com/platforms/go/",
42 Version: "2.7.3",
43 ReleaseStage: "Production",
44 Hostname: "Brian's Surface",
45 AppType: "Revel API",
46 AppVersion: "6.3.9",
47 NotifyReleaseStages: []string{"staging", "production"},
48 }
49 c.Update(&exp)
50 tt := []struct {
51 name string
52 expected interface{}
53 got interface{}
54 }{
55 {"PublishInterval", exp.PublishInterval, c.PublishInterval},
56 {"APIKey", exp.APIKey, c.APIKey},
57 {"Endpoint", exp.Endpoint, c.Endpoint},
58 {"Version", exp.Version, c.Version},
59 {"ReleaseStage", exp.ReleaseStage, c.ReleaseStage},
60 {"Hostname", exp.Hostname, c.Hostname},
61 {"AppType", exp.AppType, c.AppType},
62 {"AppVersion", exp.AppVersion, c.AppVersion},
63 {"NotifyReleaseStages", exp.NotifyReleaseStages, c.NotifyReleaseStages},
64 }
65 for _, tc := range tt {
66 if !reflect.DeepEqual(tc.got, tc.expected) {
67 t.Errorf("Expected '%s' to be '%v' but was '%v'", tc.name, tc.expected, tc.got)
68 }
69 }
70 }
71
72 func testConfig() SessionTrackingConfiguration {
73 return SessionTrackingConfiguration{
74 PublishInterval: 20 * time.Second,
75 APIKey: "api123",
76 Endpoint: "https://bugsnag.com/jobs", //If you like what you see... ;)
77 Version: "1.6.2",
78 ReleaseStage: "Staging",
79 Hostname: "Russ's MacbookPro",
80 AppType: "Gin API",
81 AppVersion: "5.2.8",
82 NotifyReleaseStages: []string{"staging", "production"},
83 Transport: http.DefaultTransport,
84 }
85 }
0 package sessions_test
1
2 import (
3 "context"
4 "io/ioutil"
5 "net/http"
6 "net/http/httptest"
7 "os"
8 "runtime"
9 "strings"
10 "sync"
11 "testing"
12 "time"
13
14 simplejson "github.com/bitly/go-simplejson"
15 bugsnag "github.com/bugsnag/bugsnag-go/v2"
16 )
17
18 const testAPIKey = "166f5ad3590596f9aa8d601ea89af845"
19 const testPublishInterval = time.Millisecond * 200
20 const sessionsCount = 50000
21
22 func init() {
23 //Naughty injection to achieve a reasonable test duration.
24 bugsnag.DefaultSessionPublishInterval = testPublishInterval
25 }
26
27 func get(j *simplejson.Json, path string) *simplejson.Json {
28 return j.GetPath(strings.Split(path, ".")...)
29 }
30 func getInt(j *simplejson.Json, path string) int {
31 return get(j, path).MustInt()
32 }
33 func getString(j *simplejson.Json, path string) string {
34 return get(j, path).MustString()
35 }
36 func getIndex(j *simplejson.Json, path string, index int) *simplejson.Json {
37 return get(j, path).GetIndex(index)
38 }
39
40 // Spins up a session server and checks that for every call to
41 // bugsnag.StartSession() a session is being recorded.
42 func TestStartSession(t *testing.T) {
43 if runtime.GOOS == "windows" {
44 t.Skip("not compatible with windows builds")
45 return
46 }
47 sessionsStarted := 0
48 mutex := sync.Mutex{}
49
50 // Test server does all the checking of individual requests
51 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
52 assertCorrectHeaders(t, r)
53 body, err := ioutil.ReadAll(r.Body)
54 if err != nil {
55 t.Error(err)
56 }
57 json, err := simplejson.NewJson(body)
58 if err != nil {
59 t.Error(err)
60 }
61 hostname, _ := os.Hostname()
62 tt := []struct {
63 prop string
64 exp interface{}
65 }{
66 {prop: "notifier.name", exp: "Bugsnag Go"},
67 {prop: "notifier.url", exp: "https://github.com/bugsnag/bugsnag-go"},
68 {prop: "notifier.version", exp: bugsnag.Version},
69 {prop: "app.releaseStage", exp: "production"},
70 {prop: "app.version", exp: ""},
71 {prop: "device.osName", exp: runtime.GOOS},
72 {prop: "device.hostname", exp: hostname},
73 {prop: "device.runtimeVersions.go", exp: runtime.Version()},
74 {prop: "device.runtimeVersions.gin", exp: ""},
75 {prop: "device.runtimeVersions.martini", exp: ""},
76 {prop: "device.runtimeVersions.negroni", exp: ""},
77 {prop: "device.runtimeVersions.revel", exp: ""},
78 }
79 for _, tc := range tt {
80 got := getString(json, tc.prop)
81 if got != tc.exp {
82 t.Errorf("Expected '%s' to be '%s' but was '%s'", tc.prop, tc.exp, got)
83 }
84 }
85 sessionCounts := getIndex(json, "sessionCounts", 0)
86 if got := getString(sessionCounts, "startedAt"); len(got) != 20 {
87 t.Errorf("Expected 'sessionCounts.startedAt' to be valid timestamp but was %s", got)
88 }
89 mutex.Lock()
90 defer mutex.Unlock()
91 sessionsStarted += getInt(sessionCounts, "sessionsStarted")
92 w.WriteHeader(http.StatusAccepted)
93 }))
94 defer ts.Close()
95
96 time.Sleep(testPublishInterval * 2) //Allow server to start
97
98 // Minimal config. API is mandatory, URLs point to the test server
99 bugsnag.Configure(bugsnag.Configuration{
100 APIKey: testAPIKey,
101 Endpoints: bugsnag.Endpoints{
102 Sessions: ts.URL,
103 Notify: ts.URL,
104 },
105 })
106 for i := 0; i < sessionsCount; i++ {
107 bugsnag.StartSession(context.Background())
108 }
109
110 time.Sleep(testPublishInterval * 2) //Allow all messages to be processed
111
112 mutex.Lock()
113 defer mutex.Unlock()
114 // Don't expect an additional session from startup as the test server URL
115 // would be different between processes
116 if got, exp := sessionsStarted, sessionsCount; got != exp {
117 t.Errorf("Expected %d sessions started, but was %d", exp, got)
118 }
119 }
120
121 func assertCorrectHeaders(t *testing.T, req *http.Request) {
122 testCases := []struct{ name, expected string }{
123 {name: "Bugsnag-Payload-Version", expected: "1.0"},
124 {name: "Content-Type", expected: "application/json"},
125 {name: "Bugsnag-Api-Key", expected: testAPIKey},
126 }
127 for _, tc := range testCases {
128 t.Run(tc.name, func(st *testing.T) {
129 if got := req.Header[tc.name][0]; tc.expected != got {
130 t.Errorf("Expected header '%s' to be '%s' but was '%s'", tc.name, tc.expected, got)
131 }
132 })
133 }
134 name := "Bugsnag-Sent-At"
135 if req.Header[name][0] == "" {
136 t.Errorf("Expected header '%s' to be non-empty but was empty", name)
137 }
138 }
0 package sessions
1
2 import (
3 "runtime"
4 "time"
5
6 "github.com/bugsnag/bugsnag-go/v2/device"
7 )
8
9 // notifierPayload defines the .notifier subobject of the payload
10 type notifierPayload struct {
11 Name string `json:"name"`
12 URL string `json:"url"`
13 Version string `json:"version"`
14 }
15
16 // appPayload defines the .app subobject of the payload
17 type appPayload struct {
18 Type string `json:"type,omitempty"`
19 ReleaseStage string `json:"releaseStage,omitempty"`
20 Version string `json:"version,omitempty"`
21 }
22
23 // devicePayload defines the .device subobject of the payload
24 type devicePayload struct {
25 OsName string `json:"osName,omitempty"`
26 Hostname string `json:"hostname,omitempty"`
27
28 RuntimeVersions *device.RuntimeVersions `json:"runtimeVersions"`
29 }
30
31 // sessionCountsPayload defines the .sessionCounts subobject of the payload
32 type sessionCountsPayload struct {
33 StartedAt string `json:"startedAt"`
34 SessionsStarted int `json:"sessionsStarted"`
35 }
36
37 // sessionPayload defines the top level payload object
38 type sessionPayload struct {
39 Notifier *notifierPayload `json:"notifier"`
40 App *appPayload `json:"app"`
41 Device *devicePayload `json:"device"`
42 SessionCounts []sessionCountsPayload `json:"sessionCounts"`
43 }
44
45 // makeSessionPayload creates a sessionPayload based off of the given sessions and config
46 func makeSessionPayload(sessions []*Session, config *SessionTrackingConfiguration) *sessionPayload {
47 releaseStage := config.ReleaseStage
48 if releaseStage == "" {
49 releaseStage = "production"
50 }
51 hostname := config.Hostname
52 if hostname == "" {
53 hostname = device.GetHostname()
54 }
55
56 return &sessionPayload{
57 Notifier: &notifierPayload{
58 Name: "Bugsnag Go",
59 URL: "https://github.com/bugsnag/bugsnag-go",
60 Version: config.Version,
61 },
62 App: &appPayload{
63 Type: config.AppType,
64 Version: config.AppVersion,
65 ReleaseStage: releaseStage,
66 },
67 Device: &devicePayload{
68 OsName: runtime.GOOS,
69 Hostname: hostname,
70 RuntimeVersions: device.GetRuntimeVersions(),
71 },
72 SessionCounts: []sessionCountsPayload{
73 {
74 //This timestamp assumes that we're sending these off once a minute
75 StartedAt: sessions[0].StartedAt.UTC().Format(time.RFC3339),
76 SessionsStarted: len(sessions),
77 },
78 },
79 }
80 }
0 package sessions
1
2 import (
3 "bytes"
4 "encoding/json"
5 "fmt"
6 "net/http"
7
8 "github.com/bugsnag/bugsnag-go/v2/headers"
9 )
10
11 // sessionPayloadVersion defines the current version of the payload that's
12 // being sent to the session server.
13 const sessionPayloadVersion = "1.0"
14
15 type sessionPublisher interface {
16 publish(sessions []*Session) error
17 }
18
19 type httpClient interface {
20 Do(*http.Request) (*http.Response, error)
21 }
22
23 type publisher struct {
24 config *SessionTrackingConfiguration
25 client httpClient
26 }
27
28 // publish builds a payload from the given sessions and publishes them to the
29 // session server. Returns any errors that happened as part of publishing.
30 func (p *publisher) publish(sessions []*Session) error {
31 if p.config.Endpoint == "" {
32 // Session tracking is disabled, likely because the notify endpoint was
33 // changed without changing the sessions endpoint
34 // We've already logged a warning in this case, so no need to spam the
35 // log every minute
36 return nil
37 }
38 if apiKey := p.config.APIKey; len(apiKey) != 32 {
39 return fmt.Errorf("bugsnag/sessions/publisher.publish invalid API key: '%s'", apiKey)
40 }
41 nrs, rs := p.config.NotifyReleaseStages, p.config.ReleaseStage
42 if rs != "" && (nrs != nil && !contains(nrs, rs)) {
43 // Always send sessions if the release stage is not set, but don't send any
44 // sessions when notify release stages don't match the current release stage
45 return nil
46 }
47 if len(sessions) == 0 {
48 return fmt.Errorf("bugsnag/sessions/publisher.publish requested publication of 0")
49 }
50 p.config.mutex.Lock()
51 defer p.config.mutex.Unlock()
52 payload := makeSessionPayload(sessions, p.config)
53 buf, err := json.Marshal(payload)
54 if err != nil {
55 return fmt.Errorf("bugsnag/sessions/publisher.publish unable to marshal json: %v", err)
56 }
57 req, err := http.NewRequest("POST", p.config.Endpoint, bytes.NewBuffer(buf))
58 if err != nil {
59 return fmt.Errorf("bugsnag/sessions/publisher.publish unable to create request: %v", err)
60 }
61 for k, v := range headers.PrefixedHeaders(p.config.APIKey, sessionPayloadVersion) {
62 req.Header.Add(k, v)
63 }
64 res, err := p.client.Do(req)
65 if err != nil {
66 return fmt.Errorf("bugsnag/sessions/publisher.publish unable to deliver session: %v", err)
67 }
68 defer func(res *http.Response) {
69 if err := res.Body.Close(); err != nil {
70 p.config.logf("%v", err)
71 }
72 }(res)
73 if res.StatusCode != 202 {
74 return fmt.Errorf("bugsnag/session.publish expected 202 response status, got HTTP %s", res.Status)
75 }
76 return nil
77 }
78
79 func contains(coll []string, e string) bool {
80 for _, s := range coll {
81 if s == e {
82 return true
83 }
84 }
85 return false
86 }
0 package sessions
1
2 import (
3 "io"
4 "io/ioutil"
5 "net/http"
6 "os"
7 "runtime"
8 "strings"
9 "testing"
10 "time"
11
12 simplejson "github.com/bitly/go-simplejson"
13 uuid "github.com/gofrs/uuid"
14 )
15
16 const (
17 sessionEndpoint = "http://localhost:9181"
18 testAPIKey = "166f5ad3590596f9aa8d601ea89af845"
19 )
20
21 type testHTTPClient struct {
22 reqs []*http.Request
23 }
24
25 // A simple io.ReadCloser that we can inject as a body of a http.Request.
26 type nopCloser struct {
27 io.Reader
28 }
29
30 func (nopCloser) Close() error { return nil }
31
32 func (c *testHTTPClient) Do(r *http.Request) (*http.Response, error) {
33 c.reqs = append(c.reqs, r)
34 return &http.Response{Body: nopCloser{}, StatusCode: 202}, nil
35 }
36
37 func get(j *simplejson.Json, path string) *simplejson.Json {
38 return j.GetPath(strings.Split(path, ".")...)
39 }
40 func getInt(j *simplejson.Json, path string) int {
41 return get(j, path).MustInt()
42 }
43 func getString(j *simplejson.Json, path string) string {
44 return get(j, path).MustString()
45 }
46 func getIndex(j *simplejson.Json, path string, index int) *simplejson.Json {
47 return get(j, path).GetIndex(index)
48 }
49
50 func TestSendsCorrectPayloadForSmallConfig(t *testing.T) {
51 sessions, earliestTime := makeSessions()
52 testClient := testHTTPClient{}
53
54 publisher := publisher{
55 config: &SessionTrackingConfiguration{Endpoint: sessionEndpoint, Transport: http.DefaultTransport, APIKey: testAPIKey},
56 client: &testClient,
57 }
58
59 err := publisher.publish(sessions)
60 if err != nil {
61 t.Error(err)
62 }
63 req := testClient.reqs[0]
64 assertCorrectHeaders(t, req)
65 body, err := ioutil.ReadAll(req.Body)
66 if err != nil {
67 t.Fatal(err)
68 }
69 root, err := simplejson.NewJson(body)
70 if err != nil {
71 t.Fatal(err)
72 }
73
74 hostname, _ := os.Hostname()
75
76 for prop, exp := range map[string]string{
77 "notifier.name": "Bugsnag Go",
78 "notifier.url": "https://github.com/bugsnag/bugsnag-go",
79 "notifier.version": "",
80 "app.type": "",
81 "app.releaseStage": "production",
82 "app.version": "",
83 "device.osName": runtime.GOOS,
84 "device.hostname": hostname,
85 } {
86 t.Run(prop, func(st *testing.T) {
87 if got := getString(root, prop); got != exp {
88 t.Errorf("Expected property '%s' in JSON to be '%v' but was '%v'", prop, exp, got)
89 }
90 })
91 }
92 sessionCounts := getIndex(root, "sessionCounts", 0)
93 if got, exp := getString(sessionCounts, "startedAt"), earliestTime; got != exp {
94 t.Errorf("Expected sessionCounts[0].startedAt to be '%s' but was '%s'", exp, got)
95 }
96 if got, exp := getInt(sessionCounts, "sessionsStarted"), len(sessions); got != exp {
97 t.Errorf("Expected sessionCounts[0].sessionsStarted to be %d but was %d", exp, got)
98 }
99 }
100
101 func TestSendsCorrectPayloadForBigConfig(t *testing.T) {
102 sessions, earliestTime := makeSessions()
103
104 testClient := testHTTPClient{}
105 publisher := publisher{
106 config: makeHeavyConfig(),
107 client: &testClient,
108 }
109
110 err := publisher.publish(sessions)
111 if err != nil {
112 t.Error(err)
113 }
114 req := testClient.reqs[0]
115 assertCorrectHeaders(t, req)
116 body, err := ioutil.ReadAll(req.Body)
117 if err != nil {
118 t.Fatal(err)
119 }
120 root, err := simplejson.NewJson(body)
121 if err != nil {
122 t.Fatal(err)
123 }
124
125 for prop, exp := range map[string]string{
126 "notifier.name": "Bugsnag Go",
127 "notifier.url": "https://github.com/bugsnag/bugsnag-go",
128 "notifier.version": "2.3.4-alpha",
129 "app.type": "gin",
130 "app.releaseStage": "development",
131 "app.version": "1.2.3-beta",
132 "device.osName": runtime.GOOS,
133 "device.hostname": "gce-1234-us-west-1",
134 } {
135 t.Run(prop, func(st *testing.T) {
136 if got := getString(root, prop); got != exp {
137 t.Errorf("Expected property '%s' in JSON to be '%v' but was '%v'", prop, exp, got)
138 }
139 })
140 }
141 sessionCounts := getIndex(root, "sessionCounts", 0)
142 if got, exp := getString(sessionCounts, "startedAt"), earliestTime; got != exp {
143 t.Errorf("Expected sessionCounts[0].startedAt to be '%s' but was '%s'", exp, got)
144 }
145 if got, exp := getInt(sessionCounts, "sessionsStarted"), len(sessions); got != exp {
146 t.Errorf("Expected sessionCounts[0].sessionsStarted to be %d but was %d", exp, got)
147 }
148 }
149
150 func TestNoSessionsSentWhenAPIKeyIsMissing(t *testing.T) {
151 sessions, _ := makeSessions()
152 config := makeHeavyConfig()
153 config.APIKey = "labracadabrador"
154 publisher := publisher{config: config, client: &testHTTPClient{}}
155 if err := publisher.publish(sessions); err != nil {
156 if got, exp := err.Error(), "bugsnag/sessions/publisher.publish invalid API key: 'labracadabrador'"; got != exp {
157 t.Errorf(`Expected error message "%s" but got "%s"`, exp, got)
158 }
159 } else {
160 t.Errorf("Expected error message but no errors were returned")
161 }
162 }
163
164 func TestNoSessionsOutsideNotifyReleaseStages(t *testing.T) {
165 sessions, _ := makeSessions()
166
167 testClient := testHTTPClient{}
168 config := makeHeavyConfig()
169 config.NotifyReleaseStages = []string{"staging", "production"}
170 publisher := publisher{
171 config: config,
172 client: &testClient,
173 }
174
175 err := publisher.publish(sessions)
176 if err != nil {
177 t.Error(err)
178 }
179 if got := len(testClient.reqs); got != 0 {
180 t.Errorf("Didn't expect any sessions being sent as as 'development' is outside of the notify release stages, but got %d sessions", got)
181 }
182 }
183
184 func TestReleaseStageNotSetSendsSessionsRegardlessOfNotifyReleaseStages(t *testing.T) {
185 sessions, _ := makeSessions()
186
187 testClient := testHTTPClient{}
188 config := makeHeavyConfig()
189 config.NotifyReleaseStages = []string{"staging", "production"}
190 config.ReleaseStage = ""
191 publisher := publisher{
192 config: config,
193 client: &testClient,
194 }
195
196 err := publisher.publish(sessions)
197 if err != nil {
198 t.Error(err)
199 }
200 if exp, got := 1, len(testClient.reqs); got != exp {
201 t.Errorf("Expected %d sessions sent when the release stage is \"\" regardless of notify release stage, but got %d", exp, got)
202 }
203 }
204
205 func makeHeavyConfig() *SessionTrackingConfiguration {
206 return &SessionTrackingConfiguration{
207 AppType: "gin",
208 APIKey: testAPIKey,
209 AppVersion: "1.2.3-beta",
210 Version: "2.3.4-alpha",
211 Endpoint: sessionEndpoint,
212 Transport: http.DefaultTransport,
213 ReleaseStage: "development",
214 Hostname: "gce-1234-us-west-1",
215 NotifyReleaseStages: []string{"development"},
216 }
217 }
218
219 func makeSessions() ([]*Session, string) {
220 earliestTime := time.Now().Add(-6 * time.Minute)
221 genUUID := func() uuid.UUID { sessionID, _ := uuid.NewV4(); return sessionID }
222 return []*Session{
223 {StartedAt: earliestTime, ID: genUUID()},
224 {StartedAt: earliestTime.Add(2 * time.Minute), ID: genUUID()},
225 {StartedAt: earliestTime.Add(4 * time.Minute), ID: genUUID()},
226 }, earliestTime.UTC().Format(time.RFC3339)
227 }
228
229 func assertCorrectHeaders(t *testing.T, req *http.Request) {
230 testCases := []struct{ name, expected string }{
231 {name: "Bugsnag-Payload-Version", expected: "1.0"},
232 {name: "Content-Type", expected: "application/json"},
233 {name: "Bugsnag-Api-Key", expected: testAPIKey},
234 }
235 for _, tc := range testCases {
236 t.Run(tc.name, func(st *testing.T) {
237 if got := req.Header[tc.name][0]; tc.expected != got {
238 t.Errorf("Expected header '%s' to be '%s' but was '%s'", tc.name, tc.expected, got)
239 }
240 })
241 }
242 name := "Bugsnag-Sent-At"
243 if req.Header[name][0] == "" {
244 t.Errorf("Expected header '%s' to be non-empty but was empty", name)
245 }
246 }
0 package sessions
1
2 import (
3 "time"
4
5 uuid "github.com/gofrs/uuid"
6 )
7
8 // EventCounts register how many handled/unhandled events have happened for
9 // this session
10 type EventCounts struct {
11 Handled int `json:"handled"`
12 Unhandled int `json:"unhandled"`
13 }
14
15 // Session represents a start time and a unique ID that identifies the session.
16 type Session struct {
17 StartedAt time.Time
18 ID uuid.UUID
19 EventCounts *EventCounts
20 }
21
22 func newSession() *Session {
23 sessionID, _ := uuid.NewV4()
24 return &Session{
25 StartedAt: time.Now(),
26 ID: sessionID,
27 EventCounts: &EventCounts{},
28 }
29 }
0 package sessions
1
2 import (
3 "context"
4 "net/http"
5 "os"
6
7 "github.com/bugsnag/panicwrap"
8 )
9
10 // SendStartupSession is called by Bugsnag on startup, which will send a
11 // session to Bugsnag and return a context to represent the session of the main
12 // goroutine. This is the session associated with any fatal panics that are
13 // caught by panicwrap.
14 func SendStartupSession(config *SessionTrackingConfiguration) context.Context {
15 ctx := context.Background()
16 session := newSession()
17 if !config.IsAutoCaptureSessions() || isApplicationProcess() {
18 return ctx
19 }
20 publisher := &publisher{
21 config: config,
22 client: &http.Client{Transport: config.Transport},
23 }
24 go publisher.publish([]*Session{session})
25 return context.WithValue(ctx, contextSessionKey, session)
26 }
27
28 // Checks to see if this is the application process, as opposed to the process
29 // that monitors for panics
30 func isApplicationProcess() bool {
31 // Application process is run first, and this will only have been set when
32 // the monitoring process runs
33 return "" == os.Getenv(panicwrap.DEFAULT_COOKIE_KEY)
34 }
0 package sessions
1
2 import (
3 "context"
4 "net/http"
5 "os"
6 "os/signal"
7 "sync"
8 "syscall"
9 "time"
10 )
11
12 const (
13 //contextSessionKey is a unique key for accessing and setting Bugsnag
14 //session data on a context.Context object
15 contextSessionKey ctxKey = 1
16 )
17
18 // ctxKey is a type alias that ensures uniqueness as a context.Context key
19 type ctxKey int
20
21 // SessionTracker exposes a method for starting sessions that are used for
22 // gauging your application's health
23 type SessionTracker interface {
24 StartSession(context.Context) context.Context
25 FlushSessions()
26 }
27
28 type sessionTracker struct {
29 sessionChannel chan *Session
30 sessions []*Session
31 config *SessionTrackingConfiguration
32 publisher sessionPublisher
33 sessionsMutex sync.Mutex
34 }
35
36 // NewSessionTracker creates a new SessionTracker based on the provided config,
37 func NewSessionTracker(config *SessionTrackingConfiguration) SessionTracker {
38 publisher := publisher{
39 config: config,
40 client: &http.Client{Transport: config.Transport},
41 }
42 st := sessionTracker{
43 sessionChannel: make(chan *Session, 1),
44 sessions: []*Session{},
45 config: config,
46 publisher: &publisher,
47 }
48 go st.processSessions()
49 return &st
50 }
51
52 // IncrementEventCountAndGetSession extracts a Bugsnag session from the given
53 // context and increments the event count of unhandled or handled events and
54 // returns the session
55 func IncrementEventCountAndGetSession(ctx context.Context, unhandled bool) *Session {
56 if s := ctx.Value(contextSessionKey); s != nil {
57 if session, ok := s.(*Session); ok && !session.StartedAt.IsZero() {
58 // It is not just getting back a default value
59 ec := session.EventCounts
60 if unhandled {
61 ec.Unhandled++
62 } else {
63 ec.Handled++
64 }
65 return session
66 }
67 }
68 return nil
69 }
70
71 func (s *sessionTracker) StartSession(ctx context.Context) context.Context {
72 session := newSession()
73 s.sessionChannel <- session
74 return context.WithValue(ctx, contextSessionKey, session)
75 }
76
77 func (s *sessionTracker) interval() time.Duration {
78 s.config.mutex.Lock()
79 defer s.config.mutex.Unlock()
80 return s.config.PublishInterval
81 }
82
83 func (s *sessionTracker) processSessions() {
84 tic := time.Tick(s.interval())
85 shutdown := shutdownSignals()
86 for {
87 select {
88 case session := <-s.sessionChannel:
89 s.appendSession(session)
90 case <-tic:
91 s.publishCollectedSessions()
92 case sig := <-shutdown:
93 s.flushSessionsAndRepeatSignal(shutdown, sig.(syscall.Signal))
94 }
95 }
96 }
97
98 func (s *sessionTracker) appendSession(session *Session) {
99 s.sessionsMutex.Lock()
100 defer s.sessionsMutex.Unlock()
101
102 s.sessions = append(s.sessions, session)
103 }
104
105 func (s *sessionTracker) publishCollectedSessions() {
106 s.sessionsMutex.Lock()
107 defer s.sessionsMutex.Unlock()
108
109 oldSessions := s.sessions
110 s.sessions = nil
111 if len(oldSessions) > 0 {
112 go func(s *sessionTracker) {
113 err := s.publisher.publish(oldSessions)
114 if err != nil {
115 s.config.logf("%v", err)
116 }
117 }(s)
118 }
119 }
120
121 func (s *sessionTracker) flushSessionsAndRepeatSignal(shutdown chan<- os.Signal, sig syscall.Signal) {
122 s.sessionsMutex.Lock()
123 defer s.sessionsMutex.Unlock()
124
125 signal.Stop(shutdown)
126 if len(s.sessions) > 0 {
127 err := s.publisher.publish(s.sessions)
128 if err != nil {
129 s.config.logf("%v", err)
130 }
131 }
132
133 if p, err := os.FindProcess(os.Getpid()); err != nil {
134 s.config.logf("%v", err)
135 } else {
136 p.Signal(sig)
137 }
138 }
139
140 func (s *sessionTracker) FlushSessions() {
141 s.sessionsMutex.Lock()
142 defer s.sessionsMutex.Unlock()
143
144 sessions := s.sessions
145 s.sessions = nil
146 if len(sessions) != 0 {
147 if err := s.publisher.publish(sessions); err != nil {
148 s.config.logf("%v", err)
149 }
150 }
151 }
152
153 func shutdownSignals() chan os.Signal {
154 c := make(chan os.Signal, 1)
155 signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
156 return c
157 }
0 package sessions
1
2 import (
3 "context"
4 "sync"
5 "testing"
6 "time"
7 )
8
9 type testPublisher struct {
10 mutex sync.Mutex
11 sessionsReceived [][]*Session
12 }
13
14 var pub = testPublisher{
15 mutex: sync.Mutex{},
16 sessionsReceived: [][]*Session{},
17 }
18
19 func (pub *testPublisher) publish(sessions []*Session) error {
20 pub.mutex.Lock()
21 defer pub.mutex.Unlock()
22 pub.sessionsReceived = append(pub.sessionsReceived, sessions)
23 return nil
24 }
25
26 func TestStartSessionModifiesContext(t *testing.T) {
27 type ctxKey string
28 var k ctxKey
29 k, v := "key", "val"
30 st, c := makeSessionTracker()
31 defer close(c)
32
33 ctx := st.StartSession(context.WithValue(context.Background(), k, v))
34 if got, exp := ctx.Value(k), v; got != exp {
35 t.Errorf("Changed pre-existing key '%s' with value '%s' into %s", k, v, got)
36 }
37 if got := ctx.Value(contextSessionKey); got == nil {
38 t.Fatalf("No session information applied to context %v", ctx)
39 }
40
41 verifyValidSession(t, IncrementEventCountAndGetSession(ctx, true))
42 }
43
44 func TestShouldOnlyWriteWhenReceivingSessions(t *testing.T) {
45 st, c := makeSessionTracker()
46 defer close(c)
47 go st.processSessions()
48 time.Sleep(10 * st.config.PublishInterval) // Would publish many times in this time period if there were sessions
49
50 if got := pub.sessionsReceived; len(got) != 0 {
51 t.Errorf("pub was invoked unexpectedly %d times with arguments: %v", len(got), got)
52 }
53
54 for i := 0; i < 50000; i++ {
55 st.StartSession(context.Background())
56 }
57 time.Sleep(st.config.PublishInterval * 2)
58
59 var sessions []*Session
60 pub.mutex.Lock()
61 defer pub.mutex.Unlock()
62 for _, s := range pub.sessionsReceived {
63 for _, session := range s {
64 verifyValidSession(t, session)
65 sessions = append(sessions, session)
66 }
67 }
68 if exp, got := 50000, len(sessions); exp != got {
69 t.Errorf("Expected %d sessions but got %d", exp, got)
70 }
71
72 }
73
74 func makeSessionTracker() (*sessionTracker, chan *Session) {
75 c := make(chan *Session, 1)
76 return &sessionTracker{
77 config: &SessionTrackingConfiguration{
78 PublishInterval: time.Millisecond * 10, //Publish very fast
79 },
80 sessionChannel: c,
81 sessions: []*Session{},
82 publisher: &pub,
83 }, c
84 }
85
86 func verifyValidSession(t *testing.T, s *Session) {
87 if (s.StartedAt == time.Time{}) {
88 t.Errorf("Expected start time to be set but was nil")
89 }
90 if len(s.ID) != 16 {
91 t.Errorf("Expected UUID to be a valid V4 UUID but was %s", s.ID)
92 }
93 }
0 // Package testutil can be .-imported to gain access to useful test functions.
1 package testutil
2
3 import (
4 "io/ioutil"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "testing"
9 "time"
10
11 simplejson "github.com/bitly/go-simplejson"
12 )
13
14 // TestAPIKey is a fake API key that can be used for testing
15 const TestAPIKey = "166f5ad3590596f9aa8d601ea89af845"
16
17 // Setup sets up and returns a test event server for receiving the event payloads.
18 // report payloads published to the returned server's URL will be put on the returned channel
19 func Setup() (*httptest.Server, chan []byte) {
20 reports := make(chan []byte, 10)
21 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22 if strings.Contains(r.URL.Path, "sessions") {
23 return
24 }
25 body, _ := ioutil.ReadAll(r.Body)
26 reports <- body
27 })), reports
28 }
29
30 // Get travels through a JSON object and returns the specified node
31 func Get(j *simplejson.Json, path string) *simplejson.Json {
32 return j.GetPath(strings.Split(path, ".")...)
33 }
34
35 // GetIndex returns the n-th element of the specified path inside the given JSON object
36 func GetIndex(j *simplejson.Json, path string, n int) *simplejson.Json {
37 return Get(j, path).GetIndex(n)
38 }
39
40 func getBool(j *simplejson.Json, path string) bool {
41 return Get(j, path).MustBool()
42 }
43 func getInt(j *simplejson.Json, path string) int {
44 return Get(j, path).MustInt()
45 }
46 func getString(j *simplejson.Json, path string) string {
47 return Get(j, path).MustString()
48 }
49
50 // AssertPayload compares the payload that was received by the event-server to
51 // the expected report JSON payload
52 func AssertPayload(t *testing.T, report *simplejson.Json, expPretty string) {
53 expReport, err := simplejson.NewJson([]byte(expPretty))
54 if err != nil {
55 t.Fatal(err)
56 }
57 expEvent := GetIndex(expReport, "events", 0)
58 expException := GetIndex(expEvent, "exceptions", 0)
59
60 event := GetIndex(report, "events", 0)
61 exception := GetIndex(event, "exceptions", 0)
62
63 if exp, got := getBool(expEvent, "unhandled"), getBool(event, "unhandled"); got != exp {
64 t.Errorf("expected 'unhandled' to be '%v' but got '%v'", exp, got)
65 }
66 for _, tc := range []struct {
67 prop string
68 got, exp *simplejson.Json
69 }{
70 {got: report, exp: expReport, prop: "apiKey"},
71 {got: report, exp: expReport, prop: "notifier.name"},
72 {got: report, exp: expReport, prop: "notifier.version"},
73 {got: report, exp: expReport, prop: "notifier.url"},
74 {got: exception, exp: expException, prop: "message"},
75 {got: exception, exp: expException, prop: "errorClass"},
76 {got: event, exp: expEvent, prop: "user.id"},
77 {got: event, exp: expEvent, prop: "severity"},
78 {got: event, exp: expEvent, prop: "severityReason.type"},
79 {got: event, exp: expEvent, prop: "metaData.request.httpMethod"},
80 {got: event, exp: expEvent, prop: "metaData.request.url"},
81 {got: event, exp: expEvent, prop: "request.httpMethod"},
82 {got: event, exp: expEvent, prop: "request.url"},
83 {got: event, exp: expEvent, prop: "request.referer"},
84 {got: event, exp: expEvent, prop: "request.headers.Accept-Encoding"},
85 } {
86 if got, exp := getString(tc.got, tc.prop), getString(tc.exp, tc.prop); got != exp {
87 t.Errorf("expected '%s' to be '%s' but was '%s'", tc.prop, exp, got)
88 }
89 }
90 assertValidSession(t, event, getBool(expEvent, "unhandled"))
91 }
92
93 func assertValidSession(t *testing.T, event *simplejson.Json, unhandled bool) {
94 if sessionID := getString(event, "session.id"); len(sessionID) != 36 {
95 t.Errorf("Expected a valid session ID to be set but was '%s'", sessionID)
96 }
97 if _, e := time.Parse(time.RFC3339, getString(event, "session.startedAt")); e != nil {
98 t.Error(e)
99 }
100 expHandled, expUnhandled := 1, 0
101 if unhandled {
102 expHandled, expUnhandled = expUnhandled, expHandled
103 }
104 if got := getInt(event, "session.events.unhandled"); got != expUnhandled {
105 t.Errorf("Expected %d unhandled events in session but was %d", expUnhandled, got)
106 }
107 if got := getInt(event, "session.events.handled"); got != expHandled {
108 t.Errorf("Expected %d handled events in session but was %d", expHandled, got)
109 }
110 }