forked from Ivasoft/traefik
Compare commits
102 Commits
v1.0.alpha
...
v1.0.alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81e9fdfe75 | ||
|
|
465bb133c7 | ||
|
|
c15d7e03b4 | ||
|
|
5bfcfeb779 | ||
|
|
faa7fd0f05 | ||
|
|
ab50b10d1b | ||
|
|
b7a71edfcb | ||
|
|
587b17c120 | ||
|
|
c46ffed846 | ||
|
|
707b6f9a95 | ||
|
|
4bdc704a25 | ||
|
|
c0fd700904 | ||
|
|
72177c676e | ||
|
|
784fd74d3f | ||
|
|
cfbd43d1ee | ||
|
|
f10bbd8c69 | ||
|
|
6bcb6f92f5 | ||
|
|
f6b5684a5b | ||
|
|
866e8db5f7 | ||
|
|
a9925c7521 | ||
|
|
f955cc33c5 | ||
|
|
e728f32a15 | ||
|
|
4abb4c6489 | ||
|
|
66998e60b8 | ||
|
|
71288e5799 | ||
|
|
8fdd0b20d1 | ||
|
|
4e9ff45747 | ||
|
|
d6e28a923c | ||
|
|
1604786285 | ||
|
|
35cb9100cd | ||
|
|
4729e3e999 | ||
|
|
b0e66a4aa6 | ||
|
|
4218467ab3 | ||
|
|
6e62625ebf | ||
|
|
c8a0a83e2b | ||
|
|
76bd04e349 | ||
|
|
a8a78b8ea3 | ||
|
|
3435ebfe42 | ||
|
|
4d485e1b6b | ||
|
|
3f905ee7d0 | ||
|
|
e90cb6b53b | ||
|
|
400655f212 | ||
|
|
481a4b2096 | ||
|
|
85bbd49798 | ||
|
|
40391c57c2 | ||
|
|
7607eb173b | ||
|
|
15318c4631 | ||
|
|
7be566ef7c | ||
|
|
3c9ec55f0a | ||
|
|
5ee6981410 | ||
|
|
c32f82baee | ||
|
|
89bb1ae835 | ||
|
|
9387235a04 | ||
|
|
7766d0ddaa | ||
|
|
cdade5f649 | ||
|
|
de0a57ec76 | ||
|
|
6e1a0554c0 | ||
|
|
ae73d08d67 | ||
|
|
ddceefa4e1 | ||
|
|
80cd6c3699 | ||
|
|
9cfd0a6b26 | ||
|
|
1e99ecf583 | ||
|
|
aae7941689 | ||
|
|
d888b4fcb5 | ||
|
|
b029e7eded | ||
|
|
6f3afe8213 | ||
|
|
b4c019afb6 | ||
|
|
143ea86ab9 | ||
|
|
287d5c59da | ||
|
|
ae6bda3220 | ||
|
|
0a6be92290 | ||
|
|
b71b5dd0d4 | ||
|
|
b12c4ac55a | ||
|
|
9f736f4235 | ||
|
|
b59c54d560 | ||
|
|
0429faf65d | ||
|
|
33d912290b | ||
|
|
d390f86de2 | ||
|
|
aaeb7cdffd | ||
|
|
32bfecff83 | ||
|
|
d671cc3821 | ||
|
|
5dea2e7902 | ||
|
|
1fdff9dae4 | ||
|
|
46d7cc83c9 | ||
|
|
539fd5bafc | ||
|
|
e8eec77df4 | ||
|
|
9a8d30a0b8 | ||
|
|
812ff77cec | ||
|
|
86f95924a9 | ||
|
|
a0df7ab921 | ||
|
|
2e5f4598f0 | ||
|
|
46e162e6a9 | ||
|
|
fd234c683c | ||
|
|
67bc87dcda | ||
|
|
c452fd2195 | ||
|
|
8f38337757 | ||
|
|
5454299bf0 | ||
|
|
80f4884d50 | ||
|
|
4ea48c2d19 | ||
|
|
37438a6395 | ||
|
|
784dc9ea62 | ||
|
|
6362b1da7f |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
vendor/
|
||||
!dist/traefik
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,6 +5,5 @@ log
|
||||
*.iml
|
||||
traefik
|
||||
traefik.toml
|
||||
|
||||
Godeps/_workspace/bin
|
||||
Godeps/_workspace/pkg
|
||||
*.test
|
||||
vendor/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM scratch
|
||||
COPY script/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY dist/traefik /
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["/traefik"]
|
||||
|
||||
302
Godeps/Godeps.json
generated
302
Godeps/Godeps.json
generated
@@ -1,302 +0,0 @@
|
||||
{
|
||||
"ImportPath": "github.com/emilevauge/traefik",
|
||||
"GoVersion": "go1.4.2",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [{
|
||||
"ImportPath": "github.com/BurntSushi/toml",
|
||||
"Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1"
|
||||
}, {
|
||||
"ImportPath": "github.com/BurntSushi/ty",
|
||||
"Rev": "6add9cd6ad42d389d6ead1dde60b4ad71e46fd74"
|
||||
}, {
|
||||
"ImportPath": "github.com/Sirupsen/logrus",
|
||||
"Comment": "v0.8.7",
|
||||
"Rev": "418b41d23a1bf978c06faea5313ba194650ac088"
|
||||
}, {
|
||||
"ImportPath": "github.com/alecthomas/template",
|
||||
"Rev": "b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0"
|
||||
}, {
|
||||
"ImportPath": "github.com/alecthomas/units",
|
||||
"Rev": "6b4e7dc5e3143b85ea77909c72caf89416fc2915"
|
||||
}, {
|
||||
"ImportPath": "github.com/boltdb/bolt",
|
||||
"Rev": "51f99c862475898df9773747d3accd05a7ca33c1"
|
||||
}, {
|
||||
"ImportPath": "github.com/cenkalti/backoff",
|
||||
"Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99"
|
||||
}, {
|
||||
"ImportPath": "github.com/codahale/hdrhistogram",
|
||||
"Rev": "954f16e8b9ef0e5d5189456aa4c1202758e04f17"
|
||||
}, {
|
||||
"ImportPath": "github.com/codegangsta/negroni",
|
||||
"Comment": "v0.1-70-gc7477ad",
|
||||
"Rev": "c7477ad8e330bef55bf1ebe300cf8aa67c492d1b"
|
||||
}, {
|
||||
"ImportPath": "github.com/coreos/go-etcd/etcd",
|
||||
"Comment": "v2.0.0-11-gcc90c7b",
|
||||
"Rev": "cc90c7b091275e606ad0ca7102a23fb2072f3f5e"
|
||||
}, {
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Rev": "2df174808ee097f90d259e432cc04442cf60be21"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/libkv",
|
||||
"Rev": "3732f7ff1b56057c3158f10bceb1e79133025373"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/distribution",
|
||||
"Comment": "v2.0.0-467-g9038e48",
|
||||
"Rev": "9038e48c3b982f8e82281ea486f078a73731ac4e"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/api",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/cliconfig",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/daemon/network",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/graph/tags",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/image",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/opts",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/archive",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/fileutils",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/homedir",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/httputils",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/jsonmessage",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/mflag",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/nat",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/parsers",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/pools",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/promise",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/random",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/stdcopy",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/stringid",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/symlink",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/system",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/tarsum",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/term",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/timeutils",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/tlsconfig",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/ulimit",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/units",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/urlutil",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/useragent",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/pkg/version",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/registry",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/runconfig",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/utils",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/docker/volume",
|
||||
"Comment": "v1.4.1-5200-gf39987a",
|
||||
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/libcompose/docker",
|
||||
"Rev": "79ef5d150f053a5b12f16b02d8844ed7cf33611a"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/libcompose/logger",
|
||||
"Rev": "79ef5d150f053a5b12f16b02d8844ed7cf33611a"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/libcompose/lookup",
|
||||
"Rev": "79ef5d150f053a5b12f16b02d8844ed7cf33611a"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/libcompose/project",
|
||||
"Rev": "79ef5d150f053a5b12f16b02d8844ed7cf33611a"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/libcompose/utils",
|
||||
"Rev": "79ef5d150f053a5b12f16b02d8844ed7cf33611a"
|
||||
}, {
|
||||
"ImportPath": "github.com/docker/libtrust",
|
||||
"Rev": "9cbd2a1374f46905c68a4eb3694a130610adc62a"
|
||||
}, {
|
||||
"ImportPath": "github.com/elazarl/go-bindata-assetfs",
|
||||
"Rev": "d5cac425555ca5cf00694df246e04f05e6a55150"
|
||||
}, {
|
||||
"ImportPath": "github.com/flynn/go-shlex",
|
||||
"Rev": "3f9db97f856818214da2e1057f8ad84803971cff"
|
||||
}, {
|
||||
"ImportPath": "github.com/fsouza/go-dockerclient",
|
||||
"Rev": "0239034d42f665efa17fd77c39f891c2f9f32922"
|
||||
}, {
|
||||
"ImportPath": "github.com/gambol99/go-marathon",
|
||||
"Rev": "0ba31bcb0d7633ba1888d744c42990eb15281cf1"
|
||||
}, {
|
||||
"ImportPath": "github.com/gorilla/context",
|
||||
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
|
||||
}, {
|
||||
"ImportPath": "github.com/gorilla/handlers",
|
||||
"Rev": "40694b40f4a928c062f56849989d3e9cd0570e5f"
|
||||
}, {
|
||||
"ImportPath": "github.com/gorilla/mux",
|
||||
"Rev": "f15e0c49460fd49eebe2bcc8486b05d1bef68d3a"
|
||||
}, {
|
||||
"ImportPath": "github.com/hashicorp/consul/api",
|
||||
"Comment": "v0.5.2-313-gde08067",
|
||||
"Rev": "de080672fee9e6104572eeea89eccdca135bb918"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/log",
|
||||
"Rev": "44874009257d4d47ba9806f1b7f72a32a015e4d8"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/manners",
|
||||
"Comment": "0.3.1-30-g37136f7",
|
||||
"Rev": "37136f736785d7c6aa3b9a27b4b2dd1028ca6d79"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/oxy/cbreaker",
|
||||
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/oxy/forward",
|
||||
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/oxy/memmetrics",
|
||||
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/oxy/roundrobin",
|
||||
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/oxy/utils",
|
||||
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/predicate",
|
||||
"Rev": "cb0bff91a7ab7cf7571e661ff883fc997bc554a3"
|
||||
}, {
|
||||
"ImportPath": "github.com/mailgun/timetools",
|
||||
"Rev": "fd192d755b00c968d312d23f521eb0cdc6f66bd0"
|
||||
}, {
|
||||
"ImportPath": "github.com/samuel/go-zookeeper/zk",
|
||||
"Rev": "fa6674abf3f4580b946a01bf7a1ce4ba8766205b"
|
||||
}, {
|
||||
"ImportPath": "github.com/opencontainers/runc/libcontainer/user",
|
||||
"Comment": "v0.0.4-21-g4ab1324",
|
||||
"Rev": "4ab132458fc3e9dbeea624153e0331952dc4c8d5"
|
||||
}, {
|
||||
"ImportPath": "github.com/samalba/dockerclient",
|
||||
"Rev": "cfb489c624b635251a93e74e1e90eb0959c5367f"
|
||||
}, {
|
||||
"ImportPath": "github.com/thoas/stats",
|
||||
"Rev": "54ed61c2b47e263ae2f01b86837b0c4bd1da28e8"
|
||||
}, {
|
||||
"ImportPath": "github.com/unrolled/render",
|
||||
"Rev": "26b4e3aac686940fe29521545afad9966ddfc80c"
|
||||
}, {
|
||||
"ImportPath": "github.com/vdemeester/shakers",
|
||||
"Rev": "8fe734f75f3a70b651cbfbf8a55a009da09e8dc5"
|
||||
}, {
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919"
|
||||
}, {
|
||||
"ImportPath": "gopkg.in/alecthomas/kingpin.v2",
|
||||
"Comment": "v2.0.12",
|
||||
"Rev": "639879d6110b1b0409410c7b737ef0bb18325038"
|
||||
}, {
|
||||
"ImportPath": "gopkg.in/check.v1",
|
||||
"Rev": "11d3bc7aa68e238947792f30573146a3231fc0f1"
|
||||
}, {
|
||||
"ImportPath": "gopkg.in/fsnotify.v1",
|
||||
"Comment": "v1.2.0",
|
||||
"Rev": "96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0"
|
||||
}, {
|
||||
"ImportPath": "gopkg.in/mgo.v2/bson",
|
||||
"Comment": "r2015.06.03-5-g22287ba",
|
||||
"Rev": "22287bab4379e1fbf6002fb4eb769888f3fb224c"
|
||||
}, {
|
||||
"ImportPath": "gopkg.in/yaml.v2",
|
||||
"Rev": "7ad95dd0798a40da1ccdff6dff35fd177b5edf40"
|
||||
}]
|
||||
}
|
||||
5
Godeps/Readme
generated
5
Godeps/Readme
generated
@@ -1,5 +0,0 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
||||
3
Godeps/_workspace/.gitignore
generated
vendored
3
Godeps/_workspace/.gitignore
generated
vendored
@@ -1,3 +0,0 @@
|
||||
/pkg
|
||||
/bin
|
||||
/src
|
||||
12
Godeps/_workspace/src/github.com/docker/docker/autogen/dockerversion/dockerversion.go
generated
vendored
12
Godeps/_workspace/src/github.com/docker/docker/autogen/dockerversion/dockerversion.go
generated
vendored
@@ -1,12 +0,0 @@
|
||||
// AUTOGENERATED FILE; see ./hack/make/.go-autogen
|
||||
package dockerversion
|
||||
|
||||
var (
|
||||
GITCOMMIT string = ""
|
||||
VERSION string = ""
|
||||
BUILDTIME string = ""
|
||||
|
||||
IAMSTATIC string = "true"
|
||||
INITSHA1 string = ""
|
||||
INITPATH string = ""
|
||||
)
|
||||
19
Makefile
19
Makefile
@@ -3,7 +3,8 @@
|
||||
TRAEFIK_ENVS := \
|
||||
-e OS_ARCH_ARG \
|
||||
-e OS_PLATFORM_ARG \
|
||||
-e TESTFLAGS
|
||||
-e TESTFLAGS \
|
||||
-e CIRCLECI
|
||||
|
||||
BIND_DIR := "dist"
|
||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
|
||||
@@ -20,10 +21,13 @@ print-%: ; @echo $*=$($*)
|
||||
|
||||
default: binary
|
||||
|
||||
binary: build
|
||||
all: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
|
||||
|
||||
binary: build generate-webui
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
|
||||
|
||||
crossbinary: build
|
||||
crossbinary: build generate-webui
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
|
||||
|
||||
test: build
|
||||
@@ -36,7 +40,7 @@ test-integration: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
|
||||
|
||||
validate: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
||||
|
||||
validate-gofmt: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
|
||||
@@ -44,7 +48,11 @@ validate-gofmt: build
|
||||
validate-govet: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-govet
|
||||
|
||||
validate-golint: build
|
||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-golint
|
||||
|
||||
build: dist
|
||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
|
||||
|
||||
build-no-cache: dist
|
||||
@@ -63,3 +71,6 @@ run-dev:
|
||||
go generate
|
||||
go build
|
||||
./traefik
|
||||
|
||||
generate-webui:
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp build
|
||||
|
||||
78
README.md
78
README.md
@@ -1,9 +1,9 @@
|
||||

|
||||
___
|
||||
|
||||
[](https://circleci.com/gh/EmileVauge/traefik)
|
||||
[](https://circleci.com/gh/emilevauge/traefik)
|
||||
[](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
|
||||
[](https://gitter.im/EmileVauge/traefik?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://traefik.herokuapp.com)
|
||||
|
||||
|
||||
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
@@ -25,7 +25,8 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
|
||||
- Tiny docker image included
|
||||
- SSL backends support
|
||||
- SSL frontend support
|
||||
- WebUI
|
||||
- Clean AngularJS Web UI
|
||||
- Websocket support
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -33,6 +34,13 @@ Here is a demo of Træfɪk using Docker backend, showing a load-balancing betwee
|
||||
|
||||
[](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
|
||||
|
||||
## Web UI
|
||||
|
||||
You can access to a simple HTML frontend of Træfik.
|
||||
|
||||

|
||||

|
||||
|
||||
## Plumbing
|
||||
|
||||
- [Oxy](https://github.com/mailgun/oxy/): an awsome proxy library made by Mailgun guys
|
||||
@@ -68,66 +76,62 @@ You can find the complete documentation [here](docs/index.md).
|
||||
|
||||
Refer to the [benchmarks section](docs/index.md#benchmarks) in the documentation.
|
||||
|
||||
## Web UI
|
||||
|
||||
You can access to a simple HTML frontend of Træfik.
|
||||
|
||||

|
||||

|
||||
|
||||
## Contributing
|
||||
|
||||
### Building
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `godep` in order to build traefik.
|
||||
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `glide` in order to build traefik.
|
||||
|
||||
#### Using Docker and Makefile
|
||||
#### Setting up your `go` environment
|
||||
|
||||
You need to run the `binary` target. This will create binaries for
|
||||
linux and darwin platforms in the `dist` folder.
|
||||
- You need `go` v1.5
|
||||
- You need to set `export GO15VENDOREXPERIMENT=1` environment variable
|
||||
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `go get github.com/jteeuwen/go-bindata/...`.
|
||||
- If you clone Træfɪk into something like `~/go/src/github.com/traefik`, your `GOPATH` variable will have to be set to `~/go`: export `GOPATH=~/go`.
|
||||
|
||||
#### Using `Docker` and `Makefile`
|
||||
|
||||
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
||||
|
||||
```bash
|
||||
$ make binary
|
||||
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
||||
# […]
|
||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/emilevauge/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate binary
|
||||
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
||||
Sending build context to Docker daemon 295.3 MB
|
||||
Step 0 : FROM golang:1.5
|
||||
---> 8c6473912976
|
||||
Step 1 : RUN go get github.com/Masterminds/glide
|
||||
[...]
|
||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/emilevauge/traefik/"dist":/go/src/github.com/emilevauge/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||
---> Making bundle: generate (in .)
|
||||
removed 'gen.go'
|
||||
|
||||
---> Making bundle: binary (in .)
|
||||
Number of parallel builds: 8
|
||||
|
||||
--> linux/arm: github.com/emilevauge/traefik
|
||||
--> darwin/amd64: github.com/emilevauge/traefik
|
||||
--> darwin/386: github.com/emilevauge/traefik
|
||||
--> linux/386: github.com/emilevauge/traefik
|
||||
--> linux/amd64: github.com/emilevauge/traefik
|
||||
|
||||
$ ls dist/
|
||||
traefik* traefik_darwin-386* traefik_darwin-amd64* traefik_linux-386* traefik_linux-amd64* traefik_linux-arm*
|
||||
traefik*
|
||||
```
|
||||
|
||||
#### Using `godep`
|
||||
#### Using `glide`
|
||||
|
||||
The idea behind `godep` is the following :
|
||||
The idea behind `glide` is the following :
|
||||
|
||||
- when checkout(ing) a project, **run `godep restore`** to install
|
||||
- when checkout(ing) a project, **run `glide up`** to install
|
||||
(`go get …`) the dependencies in the `GOPATH`.
|
||||
- if you need another dependency, `go get` it, import and use it in
|
||||
the source, and **run `godep save ./...`** to save it in
|
||||
`Godeps/Godeps.json`.
|
||||
- if you need another dependency, import and use it in
|
||||
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
|
||||
`vendor` and add it to your `glide.yaml`.
|
||||
|
||||
```bash
|
||||
$ godep restore
|
||||
# Generate
|
||||
$ godep go generate
|
||||
$ glide up --update-vendored
|
||||
# generate
|
||||
$ go generate
|
||||
# Simple go build
|
||||
$ godep go build
|
||||
$ go build
|
||||
# Using gox to build multiple platform
|
||||
$ GOPATH=`godep path`:$GOPATH gox "linux darwin" "386 amd64 arm" \
|
||||
$ gox "linux darwin" "386 amd64 arm" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
# run other commands like tests
|
||||
$ godep go test ./...
|
||||
$ go test ./...
|
||||
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
||||
```
|
||||
|
||||
|
||||
17
adapters.go
17
adapters.go
@@ -4,40 +4,37 @@ Copyright
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mailgun/oxy/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// OxyLogger implements oxy Logger interface with logrus.
|
||||
type OxyLogger struct {
|
||||
}
|
||||
|
||||
// Infof logs specified string as Debug level in logrus.
|
||||
func (oxylogger *OxyLogger) Infof(format string, args ...interface{}) {
|
||||
log.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs specified string as Warning level in logrus.
|
||||
func (oxylogger *OxyLogger) Warningf(format string, args ...interface{}) {
|
||||
log.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs specified string as Error level in logrus.
|
||||
func (oxylogger *OxyLogger) Errorf(format string, args ...interface{}) {
|
||||
log.Errorf(format, args...)
|
||||
}
|
||||
|
||||
type ErrorHandler struct {
|
||||
}
|
||||
|
||||
func (e *ErrorHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
|
||||
log.Error("server error ", err.Error())
|
||||
utils.DefaultHandler.ServeHTTP(w, req, err)
|
||||
}
|
||||
|
||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r)
|
||||
//templatesRenderer.HTML(w, http.StatusNotFound, "notFound", nil)
|
||||
}
|
||||
|
||||
// LoadDefaultConfig returns a default gorrilla.mux router from the specified configuration.
|
||||
func LoadDefaultConfig(globalConfiguration *GlobalConfiguration) *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
|
||||
|
||||
0
autogen/.placeholder
Normal file
0
autogen/.placeholder
Normal file
14
boltdb.go
14
boltdb.go
@@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
type BoltDbProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *KvProvider
|
||||
}
|
||||
|
||||
func (provider *BoltDbProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
provider.KvProvider = NewBoltDbProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
FROM golang:1.5
|
||||
FROM golang:1.5.3
|
||||
|
||||
RUN go get github.com/tools/godep
|
||||
RUN go get github.com/Masterminds/glide
|
||||
RUN go get github.com/mitchellh/gox
|
||||
RUN go get github.com/tcnksm/ghr
|
||||
RUN go get github.com/jteeuwen/go-bindata/...
|
||||
RUN go get github.com/golang/lint/golint
|
||||
|
||||
# Which docker version to test on
|
||||
ENV DOCKER_VERSION 1.6.2
|
||||
|
||||
# enable GO15VENDOREXPERIMENT
|
||||
ENV GO15VENDOREXPERIMENT 1
|
||||
|
||||
# Download docker
|
||||
RUN set -ex; \
|
||||
curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/local/bin/docker-${DOCKER_VERSION}; \
|
||||
@@ -15,17 +20,9 @@ RUN set -ex; \
|
||||
# Set the default Docker to be run
|
||||
RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
|
||||
|
||||
ENV PATH /go/src/github.com/emilevauge/traefik/Godeps/_workspace/bin:$PATH
|
||||
|
||||
WORKDIR /go/src/github.com/emilevauge/traefik
|
||||
|
||||
# This is a hack (see libcompose#32) - will be removed when libcompose will be fixed
|
||||
# (i.e go get able)
|
||||
RUN mkdir -p /go/src/github.com/docker/docker/autogen/dockerversion/
|
||||
COPY Godeps/_workspace/src/github.com/docker/docker/autogen/dockerversion/dockerversion.go /go/src/github.com/docker/docker/autogen/dockerversion/dockerversion.go
|
||||
|
||||
RUN mkdir Godeps
|
||||
COPY Godeps/Godeps.json Godeps/
|
||||
RUN godep restore
|
||||
COPY glide.yaml glide.yaml
|
||||
RUN glide up
|
||||
|
||||
COPY . /go/src/github.com/emilevauge/traefik
|
||||
|
||||
10
circle.yml
10
circle.yml
@@ -6,6 +6,8 @@ machine:
|
||||
environment:
|
||||
REPO: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
|
||||
DOCKER_HOST: tcp://172.17.42.1:2375
|
||||
MAKE_DOCKER_HOST: $DOCKER_HOST
|
||||
VERSION: v1.0.alpha.$CIRCLE_BUILD_NUM
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
@@ -18,7 +20,7 @@ dependencies:
|
||||
test:
|
||||
override:
|
||||
- make test-unit
|
||||
- make MAKE_DOCKER_HOST=$DOCKER_HOST test-integration
|
||||
- make test-integration
|
||||
post:
|
||||
- make crossbinary
|
||||
- make image
|
||||
@@ -27,8 +29,8 @@ deployment:
|
||||
hub:
|
||||
branch: master
|
||||
commands:
|
||||
- ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --prerelease v1.0.alpha.$CIRCLE_BUILD_NUM dist/
|
||||
- ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --prerelease ${VERSION} dist/
|
||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
- docker push ${REPO,,}:latest
|
||||
- docker tag ${REPO,,}:latest ${REPO,,}:v1.0.alpha.$CIRCLE_BUILD_NUM
|
||||
- docker push ${REPO,,}:v1.0.alpha.$CIRCLE_BUILD_NUM
|
||||
- docker tag ${REPO,,}:latest ${REPO,,}:${VERSION}
|
||||
- docker push ${REPO,,}:${VERSION}
|
||||
|
||||
119
configuration.go
119
configuration.go
@@ -1,102 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
fmtlog "log"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/emilevauge/traefik/provider"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||
type GlobalConfiguration struct {
|
||||
Port string
|
||||
GraceTimeOut int64
|
||||
AccessLogsFile string
|
||||
TraefikLogsFile string
|
||||
CertFile, KeyFile string
|
||||
LogLevel string
|
||||
Docker *DockerProvider
|
||||
File *FileProvider
|
||||
Web *WebProvider
|
||||
Marathon *MarathonProvider
|
||||
Consul *ConsulProvider
|
||||
Etcd *EtcdProvider
|
||||
Zookeeper *ZookepperProvider
|
||||
Boltdb *BoltDbProvider
|
||||
Port string
|
||||
GraceTimeOut int64
|
||||
AccessLogsFile string
|
||||
TraefikLogsFile string
|
||||
Certificates []Certificate
|
||||
LogLevel string
|
||||
ProvidersThrottleDuration time.Duration
|
||||
Docker *provider.Docker
|
||||
File *provider.File
|
||||
Web *WebProvider
|
||||
Marathon *provider.Marathon
|
||||
Consul *provider.Consul
|
||||
Etcd *provider.Etcd
|
||||
Zookeeper *provider.Zookepper
|
||||
Boltdb *provider.BoltDb
|
||||
}
|
||||
|
||||
// Certificate holds a SSL cert/key pair
|
||||
type Certificate struct {
|
||||
CertFile string
|
||||
KeyFile string
|
||||
}
|
||||
|
||||
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
||||
globalConfiguration := new(GlobalConfiguration)
|
||||
// default values
|
||||
globalConfiguration.Port = ":80"
|
||||
globalConfiguration.GraceTimeOut = 10
|
||||
globalConfiguration.LogLevel = "ERROR"
|
||||
globalConfiguration.ProvidersThrottleDuration = time.Duration(2 * time.Second)
|
||||
|
||||
return globalConfiguration
|
||||
}
|
||||
|
||||
// Backend configuration
|
||||
type Backend struct {
|
||||
Servers map[string]Server `json:"servers,omitempty"`
|
||||
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||
LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"`
|
||||
}
|
||||
|
||||
// LoadBalancer configuration
|
||||
type LoadBalancer struct {
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
||||
|
||||
// CircuitBreaker configuration
|
||||
type CircuitBreaker struct {
|
||||
Expression string `json:"expression,omitempty"`
|
||||
}
|
||||
|
||||
// Server configuration
|
||||
type Server struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
}
|
||||
|
||||
// Route configuration
|
||||
type Route struct {
|
||||
Rule string `json:"rule,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// Frontend configuration
|
||||
type Frontend struct {
|
||||
Backend string `json:"backend,omitempty"`
|
||||
Routes map[string]Route `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
// Configuration of a provider
|
||||
type Configuration struct {
|
||||
Backends map[string]*Backend `json:"backends,omitempty"`
|
||||
Frontends map[string]*Frontend `json:"frontends,omitempty"`
|
||||
}
|
||||
|
||||
// Load Balancer Method
|
||||
type LoadBalancerMethod uint8
|
||||
|
||||
const (
|
||||
// wrr (default) = Weighted Round Robin
|
||||
wrr LoadBalancerMethod = iota
|
||||
// drr = Dynamic Round Robin
|
||||
drr
|
||||
)
|
||||
|
||||
var loadBalancerMethodNames = []string{
|
||||
"wrr",
|
||||
"drr",
|
||||
}
|
||||
|
||||
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
|
||||
if loadBalancer != nil {
|
||||
for i, name := range loadBalancerMethodNames {
|
||||
if strings.EqualFold(name, loadBalancer.Method) {
|
||||
return LoadBalancerMethod(i), nil
|
||||
}
|
||||
}
|
||||
// LoadFileConfig returns a GlobalConfiguration from reading the specified file (a toml file).
|
||||
func LoadFileConfig(file string) *GlobalConfiguration {
|
||||
configuration := NewGlobalConfiguration()
|
||||
if _, err := toml.DecodeFile(file, configuration); err != nil {
|
||||
fmtlog.Fatalf("Error reading file: %s", err)
|
||||
}
|
||||
return wrr, ErrInvalidLoadBalancerMethod
|
||||
return configuration
|
||||
}
|
||||
|
||||
var ErrInvalidLoadBalancerMethod = errors.New("Invalid method, using default")
|
||||
type configs map[string]*types.Configuration
|
||||
|
||||
14
consul.go
14
consul.go
@@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
type ConsulProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *KvProvider
|
||||
}
|
||||
|
||||
func (provider *ConsulProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
provider.KvProvider = NewConsulProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
||||
6
contrib/systemd/traefik.service
Normal file
6
contrib/systemd/traefik.service
Normal file
@@ -0,0 +1,6 @@
|
||||
[Unit]
|
||||
Description=Traefik
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/traefik /etc/traefik.toml
|
||||
Restart=on-failure
|
||||
197
docker.go
197
docker.go
@@ -1,197 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
type DockerProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Filename string
|
||||
Domain string
|
||||
}
|
||||
|
||||
func (provider *DockerProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
if dockerClient, err := docker.NewClient(provider.Endpoint); err != nil {
|
||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||
return err
|
||||
} else {
|
||||
err := dockerClient.Ping()
|
||||
if err != nil {
|
||||
log.Errorf("Docker connection error %+v", err)
|
||||
return err
|
||||
}
|
||||
log.Debug("Docker connection established")
|
||||
if provider.Watch {
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
dockerClient.AddEventListener(dockerEvents)
|
||||
log.Debug("Docker listening")
|
||||
go func() {
|
||||
operation := func() error {
|
||||
for {
|
||||
event := <-dockerEvents
|
||||
if event == nil {
|
||||
return errors.New("Docker event nil")
|
||||
// log.Fatalf("Docker connection error")
|
||||
}
|
||||
if event.Status == "start" || event.Status == "die" {
|
||||
log.Debugf("Docker event receveived %+v", event)
|
||||
configuration := provider.loadDockerConfig(dockerClient)
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{"docker", configuration}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
configuration := provider.loadDockerConfig(dockerClient)
|
||||
configurationChan <- configMessage{"docker", configuration}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.backend" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return getHost(container)
|
||||
},
|
||||
"getPort": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.port" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
for key := range container.NetworkSettings.Ports {
|
||||
return key.Port()
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"getWeight": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.weight" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "0"
|
||||
},
|
||||
"getDomain": func(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.domain" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return provider.Domain
|
||||
},
|
||||
"replace": func(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
},
|
||||
"getHost": getHost,
|
||||
}
|
||||
configuration := new(Configuration)
|
||||
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
|
||||
containersInspected := []docker.Container{}
|
||||
hosts := map[string][]docker.Container{}
|
||||
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
containerInspected, _ := dockerClient.InspectContainer(container.ID)
|
||||
containersInspected = append(containersInspected, *containerInspected)
|
||||
}
|
||||
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(func(container docker.Container) bool {
|
||||
if len(container.NetworkSettings.Ports) == 0 {
|
||||
log.Debugf("Filtering container without port %s", container.Name)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
if container.Config.Labels["traefik.enable"] == "false" {
|
||||
log.Debugf("Filtering disabled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, containersInspected).([]docker.Container)
|
||||
|
||||
for _, container := range filteredContainers {
|
||||
hosts[getHost(container)] = append(hosts[getHost(container)], container)
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Containers []docker.Container
|
||||
Hosts map[string][]docker.Container
|
||||
Domain string
|
||||
}{
|
||||
filteredContainers,
|
||||
hosts,
|
||||
provider.Domain,
|
||||
}
|
||||
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := Asset("providerTemplates/docker.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err := tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Error("Error with docker template", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
log.Error("Error creating docker configuration", err)
|
||||
return nil
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func getHost(container docker.Container) string {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == "traefik.host" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return strings.Replace(strings.Replace(container.Name, "/", "", -1), ".", "-", -1)
|
||||
}
|
||||
@@ -25,7 +25,7 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
|
||||
|
||||
Basically, Træfɪk is a http router, which sends traffic from frontends to http backends, following rules you have configured.
|
||||
|
||||
### Frontends
|
||||
### <a id="frontends"></a> Frontends
|
||||
|
||||
Frontends can be defined using the following rules:
|
||||
|
||||
@@ -38,6 +38,7 @@ Frontends can be defined using the following rules:
|
||||
|
||||
|
||||
A frontend is a set of rules that forwards the incoming http traffic to a backend.
|
||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||
|
||||
### HTTP Backends
|
||||
|
||||
@@ -101,12 +102,24 @@ For example:
|
||||
#
|
||||
# logLevel = "ERROR"
|
||||
|
||||
# SSL certificate and key used
|
||||
# SSL certificates and keys
|
||||
# You may add several certificate/key pairs to terminate HTTPS for multiple domain names using TLS SNI
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [[certificates]]
|
||||
# CertFile = "traefik.crt"
|
||||
# KeyFile = "traefik.key"
|
||||
|
||||
# Backends throttle duration: minimum duration between 2 events from providers
|
||||
# before applying a new configuration. It avoids unnecessary reloads if multiples events
|
||||
# are sent in a short amount of time.
|
||||
#
|
||||
# Optional
|
||||
# Default: "2s"
|
||||
#
|
||||
# ProvidersThrottleDuration = "5s"
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -153,6 +166,7 @@ logLevel = "DEBUG"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
@@ -200,6 +214,7 @@ filename = "rules.toml"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
@@ -228,6 +243,11 @@ address = ":8080"
|
||||
#
|
||||
# CertFile = "traefik.crt"
|
||||
# KeyFile = "traefik.key"
|
||||
#
|
||||
# Set REST API to read-only mode
|
||||
#
|
||||
# Optional
|
||||
# ReadOnly = false
|
||||
```
|
||||
|
||||
- `/`: provides a simple HTML frontend of Træfik
|
||||
@@ -391,17 +411,29 @@ watch = true
|
||||
# Optional
|
||||
#
|
||||
# filename = "docker.tmpl"
|
||||
|
||||
# Enable docker TLS connection
|
||||
#
|
||||
# [docker.tls]
|
||||
# ca = "/etc/ssl/ca.crt"
|
||||
# cert = "/etc/ssl/docker.crt"
|
||||
# key = "/etc/ssl/docker.key"
|
||||
# insecureskipverify = true
|
||||
```
|
||||
|
||||
Labels can be used on containers to override default behaviour:
|
||||
|
||||
- `traefik.backend=foo`: assign the container to `foo` backend
|
||||
- `traefik.port=80`: register this port. Useful when the container exposes multiples ports.
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
- `traefik.weight=10`: assign this weight to the container
|
||||
- `traefik.enable=false`: disable this container in Træfɪk
|
||||
- `traefik.host=bar`: override the default routing from `{containerName}.{domain}` to `bar.{domain}`
|
||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{containerName}.{domain}`) See [frontends](#frontends).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
* `traefik.domain=traefik.localhost`: override the default domain
|
||||
|
||||
|
||||
## <a id="marathon"></a> Marathon backend
|
||||
|
||||
Træfɪk can be configured to use Marathon as a backend configuration:
|
||||
@@ -455,11 +487,14 @@ domain = "marathon.localhost"
|
||||
Labels can be used on containers to override default behaviour:
|
||||
|
||||
- `traefik.backend=foo`: assign the application to `foo` backend
|
||||
- `traefik.port=80`: register this port. Useful when the application exposes multiples ports.
|
||||
- `traefik.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports.
|
||||
- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`.
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
- `traefik.weight=10`: assign this weight to the application
|
||||
- `traefik.enable=false`: disable this application in Træfɪk
|
||||
- `traefik.host=bar`: override the default routing from `{appName}.{domain}` to `bar.{domain}`
|
||||
- `traefik.prefixes=pf1,pf2`: use `PathPrefix(es)` instead of hostname for routing, use `filename="providerTemplates/marathon-prefix.tmpl"` with this option
|
||||
- `traefik.frontend.rule=Host`: override the default frontend rule (Default: Host). See [frontends](#frontends).
|
||||
- `traefik.frontend.value=test.example.com`: override the default frontend value (Default: `{appName}.{domain}`) See [frontends](#frontends).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
* `traefik.domain=traefik.localhost`: override the default domain
|
||||
|
||||
## <a id="consul"></a> Consul backend
|
||||
@@ -481,7 +516,7 @@ Træfɪk can be configured to use Consul as a backend configuration:
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "http://127.0.0.1:8500"
|
||||
endpoint = "127.0.0.1:8500"
|
||||
|
||||
# Enable watch Consul changes
|
||||
#
|
||||
@@ -537,6 +572,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||
|
||||
@@ -616,6 +652,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||
|
||||
@@ -694,6 +731,7 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
|
||||
| Key | Value |
|
||||
|----------------------------------------------------|------------|
|
||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path` |
|
||||
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
|
||||
|
||||
|
||||
14
etcd.go
14
etcd.go
@@ -1,14 +0,0 @@
|
||||
package main
|
||||
|
||||
type EtcdProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
KvProvider *KvProvider
|
||||
}
|
||||
|
||||
func (provider *EtcdProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
provider.KvProvider = NewEtcdProvider(provider)
|
||||
return provider.KvProvider.provide(configurationChan)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package main
|
||||
@@ -2,8 +2,10 @@
|
||||
Copyright
|
||||
*/
|
||||
|
||||
//go:generate go get github.com/jteeuwen/go-bindata/...
|
||||
//go:generate rm -vf gen.go
|
||||
//go:generate go-bindata -o gen.go static/... templates/... providerTemplates/...
|
||||
//go:generate rm -vf autogen/gen.go
|
||||
//go:generate go-bindata -pkg autogen -o autogen/gen.go ./static/... ./templates/...
|
||||
|
||||
//go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion
|
||||
//go:generate cp script/dockerversion vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go
|
||||
|
||||
package main
|
||||
|
||||
149
glide.yaml
Normal file
149
glide.yaml
Normal file
@@ -0,0 +1,149 @@
|
||||
package: main
|
||||
import:
|
||||
- package: github.com/coreos/go-etcd
|
||||
ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
||||
subpackages:
|
||||
- etcd
|
||||
- package: github.com/docker/distribution
|
||||
ref: 9038e48c3b982f8e82281ea486f078a73731ac4e
|
||||
- package: github.com/mailgun/log
|
||||
ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||
- package: github.com/mailgun/oxy
|
||||
ref: 547c334d658398c05b346c0b79d8f47ba2e1473b
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- forward
|
||||
- memmetrics
|
||||
- roundrobin
|
||||
- utils
|
||||
- package: github.com/hashicorp/consul
|
||||
ref: de080672fee9e6104572eeea89eccdca135bb918
|
||||
subpackages:
|
||||
- api
|
||||
- package: github.com/samuel/go-zookeeper
|
||||
ref: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
||||
subpackages:
|
||||
- zk
|
||||
- package: github.com/docker/libtrust
|
||||
ref: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
- package: gopkg.in/check.v1
|
||||
ref: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||
- package: golang.org/x/net
|
||||
ref: d9558e5c97f85372afee28cf2b6059d7d3818919
|
||||
subpackages:
|
||||
- context
|
||||
- package: github.com/gorilla/handlers
|
||||
ref: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
||||
- package: github.com/docker/libkv
|
||||
ref: 3732f7ff1b56057c3158f10bceb1e79133025373
|
||||
- package: github.com/alecthomas/template
|
||||
ref: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||
- package: github.com/vdemeester/shakers
|
||||
ref: 8fe734f75f3a70b651cbfbf8a55a009da09e8dc5
|
||||
- package: github.com/alecthomas/units
|
||||
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
||||
- package: github.com/gambol99/go-marathon
|
||||
ref: 8ce3f764250b2de3f2c627d12ca7dd21bd5e7f93
|
||||
- package: github.com/mailgun/predicate
|
||||
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
||||
- package: github.com/thoas/stats
|
||||
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
||||
- package: github.com/samalba/dockerclient
|
||||
ref: cfb489c624b635251a93e74e1e90eb0959c5367f
|
||||
- package: github.com/Sirupsen/logrus
|
||||
ref: 418b41d23a1bf978c06faea5313ba194650ac088
|
||||
- package: github.com/unrolled/render
|
||||
ref: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||
- package: github.com/flynn/go-shlex
|
||||
ref: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
- package: github.com/fsouza/go-dockerclient
|
||||
ref: 0239034d42f665efa17fd77c39f891c2f9f32922
|
||||
- package: github.com/boltdb/bolt
|
||||
ref: 51f99c862475898df9773747d3accd05a7ca33c1
|
||||
- package: gopkg.in/mgo.v2
|
||||
ref: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||
subpackages:
|
||||
- bson
|
||||
- package: github.com/docker/docker
|
||||
ref: f39987afe8d611407887b3094c03d6ba6a766a67
|
||||
subpackages:
|
||||
- autogen
|
||||
- api
|
||||
- cliconfig
|
||||
- daemon/network
|
||||
- graph/tags
|
||||
- image
|
||||
- opts
|
||||
- pkg/archive
|
||||
- pkg/fileutils
|
||||
- pkg/homedir
|
||||
- pkg/httputils
|
||||
- pkg/ioutils
|
||||
- pkg/jsonmessage
|
||||
- pkg/mflag
|
||||
- pkg/nat
|
||||
- pkg/parsers
|
||||
- pkg/pools
|
||||
- pkg/promise
|
||||
- pkg/random
|
||||
- pkg/stdcopy
|
||||
- pkg/stringid
|
||||
- pkg/symlink
|
||||
- pkg/system
|
||||
- pkg/tarsum
|
||||
- pkg/term
|
||||
- pkg/timeutils
|
||||
- pkg/tlsconfig
|
||||
- pkg/ulimit
|
||||
- pkg/units
|
||||
- pkg/urlutil
|
||||
- pkg/useragent
|
||||
- pkg/version
|
||||
- registry
|
||||
- runconfig
|
||||
- utils
|
||||
- volume
|
||||
- package: github.com/mailgun/timetools
|
||||
ref: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||
- package: github.com/codegangsta/negroni
|
||||
ref: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
||||
- package: gopkg.in/yaml.v2
|
||||
ref: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
|
||||
- package: github.com/opencontainers/runc
|
||||
ref: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- package: github.com/gorilla/mux
|
||||
ref: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
||||
- package: github.com/BurntSushi/ty
|
||||
ref: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||
- package: github.com/elazarl/go-bindata-assetfs
|
||||
ref: d5cac425555ca5cf00694df246e04f05e6a55150
|
||||
- package: github.com/BurntSushi/toml
|
||||
ref: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
|
||||
- package: gopkg.in/alecthomas/kingpin.v2
|
||||
ref: 639879d6110b1b0409410c7b737ef0bb18325038
|
||||
- package: github.com/docker/libcompose
|
||||
ref: 79ef5d150f053a5b12f16b02d8844ed7cf33611a
|
||||
subpackages:
|
||||
- docker
|
||||
- logger
|
||||
- lookup
|
||||
- project
|
||||
- utils
|
||||
- package: github.com/cenkalti/backoff
|
||||
ref: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
||||
- package: gopkg.in/fsnotify.v1
|
||||
ref: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
||||
- package: github.com/mailgun/manners
|
||||
ref: 37136f736785d7c6aa3b9a27b4b2dd1028ca6d79
|
||||
- package: github.com/gorilla/context
|
||||
ref: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
||||
- package: github.com/codahale/hdrhistogram
|
||||
ref: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
||||
- package: github.com/gorilla/websocket
|
||||
- package: github.com/donovanhide/eventsource
|
||||
ref: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
||||
- package: github.com/golang/glog
|
||||
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// SimpleSuite
|
||||
type SimpleSuite struct{ BaseSuite }
|
||||
|
||||
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary)
|
||||
output, err := cmd.CombinedOutput()
|
||||
@@ -37,6 +40,7 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/simple_default.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
@@ -45,7 +49,18 @@ func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||
// Expected a 404 as we did not comfigure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/simple_web.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
resp, err := http.Get("http://127.0.0.1:8080/api")
|
||||
// Expected a 200
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/consul/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
@@ -21,7 +22,4 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
// Expected a 404 as we did not comfigure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/namesgenerator"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
// Label added to started container to identify them as part of the integration test
|
||||
TestLabel = "io.traefik.test"
|
||||
|
||||
// Images to have or pull before the build in order to make it work
|
||||
// FIXME handle this offline but loading them before build
|
||||
RequiredImages = map[string]string{
|
||||
"swarm": "1.0.0",
|
||||
"nginx": "1",
|
||||
}
|
||||
)
|
||||
|
||||
// Docker test suites
|
||||
type DockerSuite struct {
|
||||
BaseSuite
|
||||
client *docker.Client
|
||||
}
|
||||
|
||||
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
|
||||
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
|
||||
Config: &docker.Config{
|
||||
Image: image,
|
||||
Cmd: args,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels map[string]string, args ...string) string {
|
||||
return s.startContainerWithConfig(c, docker.CreateContainerOptions{
|
||||
Config: &docker.Config{
|
||||
Image: image,
|
||||
Cmd: args,
|
||||
Labels: labels,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *DockerSuite) startContainerWithConfig(c *check.C, config docker.CreateContainerOptions) string {
|
||||
if config.Name == "" {
|
||||
config.Name = namesgenerator.GetRandomName(10)
|
||||
}
|
||||
if config.Config.Labels == nil {
|
||||
config.Config.Labels = map[string]string{}
|
||||
}
|
||||
config.Config.Labels[TestLabel] = "true"
|
||||
|
||||
container, err := s.client.CreateContainer(config)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error creating a container using config %v", config))
|
||||
|
||||
err = s.client.StartContainer(container.ID, &docker.HostConfig{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error starting container %v", container))
|
||||
|
||||
return container.Name
|
||||
}
|
||||
|
||||
func (s *DockerSuite) SetUpSuite(c *check.C) {
|
||||
dockerHost := os.Getenv("DOCKER_HOST")
|
||||
if dockerHost == "" {
|
||||
// FIXME Handle windows -- see if dockerClient already handle that or not
|
||||
dockerHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket)
|
||||
}
|
||||
// Make sure we can speak to docker
|
||||
dockerClient, err := docker.NewClient(dockerHost)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error connecting to docker daemon"))
|
||||
|
||||
s.client = dockerClient
|
||||
c.Assert(s.client.Ping(), checker.IsNil)
|
||||
|
||||
// Pull required images
|
||||
for repository, tag := range RequiredImages {
|
||||
image := fmt.Sprintf("%s:%s", repository, tag)
|
||||
_, err := s.client.InspectImage(image)
|
||||
if err != nil {
|
||||
if err != docker.ErrNoSuchImage {
|
||||
c.Fatalf("Error while inspect image %s", image)
|
||||
}
|
||||
err = s.client.PullImage(docker.PullImageOptions{
|
||||
Repository: repository,
|
||||
Tag: tag,
|
||||
}, docker.AuthConfiguration{})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error while pulling image %s", image))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) cleanContainers(c *check.C) {
|
||||
// Clean the mess, a.k.a. the running containers with the right label
|
||||
containerList, err := s.client.ListContainers(docker.ListContainersOptions{
|
||||
Filters: map[string][]string{
|
||||
"label": {fmt.Sprintf("%s=true", TestLabel)},
|
||||
},
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error listing containers started by traefik"))
|
||||
|
||||
for _, container := range containerList {
|
||||
err = s.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: container.ID,
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error killing container %v", container))
|
||||
if os.Getenv("CIRCLECI") == "" {
|
||||
// On circleci, we won't delete them — it errors out for now >_<
|
||||
err = s.client.RemoveContainer(docker.RemoveContainerOptions{
|
||||
ID: container.ID,
|
||||
RemoveVolumes: true,
|
||||
})
|
||||
c.Assert(err, checker.IsNil, check.Commentf("Error removing container %v", container))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TearDownTest(c *check.C) {
|
||||
s.cleanContainers(c)
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TearDownSuite(c *check.C) {
|
||||
// Call cleanContainers, just in case (?)
|
||||
// s.cleanContainers(c)
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
|
||||
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
|
||||
defer os.Remove(file)
|
||||
@@ -17,15 +142,110 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1/")
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
// Expected a 404 as we did not comfigure anything
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
|
||||
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
|
||||
defer os.Remove(file)
|
||||
name := s.startContainer(c, "swarm:1.0.0", "manage", "token://blablabla")
|
||||
|
||||
// Start traefik
|
||||
cmd := exec.Command(traefikBinary, file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = fmt.Sprintf("%s.docker.localhost", name)
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
var version map[string]interface{}
|
||||
|
||||
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
|
||||
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
|
||||
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
|
||||
defer os.Remove(file)
|
||||
// Start a container with some labels
|
||||
labels := map[string]string{
|
||||
"traefik.frontend.rule": "Host",
|
||||
"traefik.frontend.value": "my.super.host",
|
||||
}
|
||||
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
||||
|
||||
// Start traefik
|
||||
cmd := exec.Command(traefikBinary, file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = fmt.Sprintf("my.super.host")
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
var version map[string]interface{}
|
||||
|
||||
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
|
||||
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
|
||||
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
|
||||
defer os.Remove(file)
|
||||
// Start a container with some labels
|
||||
labels := map[string]string{
|
||||
"traefik.frontend.value": "my.super.host",
|
||||
}
|
||||
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
|
||||
|
||||
// Start traefik
|
||||
cmd := exec.Command(traefikBinary, file)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", "http://127.0.0.1/version", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Host = fmt.Sprintf("my.super.host")
|
||||
resp, err := client.Do(req)
|
||||
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -13,15 +13,27 @@ func (s *FileSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/file/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
resp, err := http.Get("http://127.0.0.1/")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
||||
|
||||
// #56 regression test, make sure it does not fail
|
||||
func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/file/56-simple-panic.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
resp, err := http.Get("http://127.0.0.1/")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
||||
11
integration/fixtures/file/56-simple-panic.toml
Normal file
11
integration/fixtures/file/56-simple-panic.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Reverse proxy port
|
||||
#
|
||||
# Optional
|
||||
# Default: ":80"
|
||||
#
|
||||
# port = ":80"
|
||||
#
|
||||
# LogLevel
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[file]
|
||||
32
integration/fixtures/https/https_sni.toml
Normal file
32
integration/fixtures/https/https_sni.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
port = ":443"
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[[certificates]]
|
||||
CertFile = "fixtures/https/snitest.com.cert"
|
||||
KeyFile = "fixtures/https/snitest.com.key"
|
||||
|
||||
[[certificates]]
|
||||
CertFile = "fixtures/https/snitest.org.cert"
|
||||
KeyFile = "fixtures/https/snitest.org.key"
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:9010"
|
||||
[backends.backend2]
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://127.0.0.1:9020"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host"
|
||||
value = "snitest.com"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Host"
|
||||
value = "snitest.org"
|
||||
19
integration/fixtures/https/snitest.com.cert
Normal file
19
integration/fixtures/https/snitest.com.cert
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC/zCCAeegAwIBAgIJAL858pci5XyjMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
|
||||
BAMMC3NuaXRlc3QuY29tMB4XDTE1MTEyMzIyMDU1NloXDTI1MTEyMDIyMDU1Nlow
|
||||
FjEUMBIGA1UEAwwLc25pdGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQDVF25wEroSIUO/dNgHxlyt8pZFVpJ8fNaJw7cnlZ1JP2hLuEbmjAFT
|
||||
dHqS8wKDNYHktsBEOUfN/qbk0AiGb+SvhQw6kfM/QSj9fXVQ7KhYP9XYOekTOH7d
|
||||
M0Z2L3RGgqs8z+83exOOnAFVvIJCMZJXEeijV6iJlmpCcJa0Kg/JHlxhoWTEeZuU
|
||||
G+hITafk1yWOKorTCPlMhB30wuQoWfbHP+3G0bsERLXFiMANE8EtQu8+ZhfseBUh
|
||||
5Tu5gIC4Fnria7mRixAZeEiAblFP9h0vrNRcP3nmuVz0tHPIeQsJQiEhxaZ09oUW
|
||||
h9WqTsYCP1821+SVazM9oFRTpy6chZyTAgMBAAGjUDBOMB0GA1UdDgQWBBSz9mbX
|
||||
ia1TM5FG4Zgagaet24S8HDAfBgNVHSMEGDAWgBSz9mbXia1TM5FG4Zgagaet24S8
|
||||
HDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB79W8XTxlozh9w/W7T
|
||||
5vDev67G4T/wetABSb68CRrqojt78PMJuS89JarA8I3ts00O+0JYnsHxp+9qC7pf
|
||||
jWHcDSiLwRUMu7MXW/KIen1EB8BQNA0xWbAiQaWYPHzsBlX48+9wBe0HTDx7Lcxb
|
||||
OsmnXHBF5fd2EY+R8qJu+PyTDDL1WLItFJpzHiFiGiYF8Tyic3kkPjje6eIOxRmT
|
||||
hq+qbwApzbzz6h/VD5xR3zBDFBo2Xs5tdP264KIw/YXDpaXVBiJ5DDjQ3dtJw1G5
|
||||
yzgrHQZWJN8Gs8ZZgGdgRf7PHox8xEZtqPiMkChDz6T7Ha3U0xYN6TZGNZOR6DHs
|
||||
K9/8
|
||||
-----END CERTIFICATE-----
|
||||
27
integration/fixtures/https/snitest.com.key
Normal file
27
integration/fixtures/https/snitest.com.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7hG
|
||||
5owBU3R6kvMCgzWB5LbARDlHzf6m5NAIhm/kr4UMOpHzP0Eo/X11UOyoWD/V2Dnp
|
||||
Ezh+3TNGdi90RoKrPM/vN3sTjpwBVbyCQjGSVxHoo1eoiZZqQnCWtCoPyR5cYaFk
|
||||
xHmblBvoSE2n5NcljiqK0wj5TIQd9MLkKFn2xz/txtG7BES1xYjADRPBLULvPmYX
|
||||
7HgVIeU7uYCAuBZ64mu5kYsQGXhIgG5RT/YdL6zUXD955rlc9LRzyHkLCUIhIcWm
|
||||
dPaFFofVqk7GAj9fNtfklWszPaBUU6cunIWckwIDAQABAoIBABAdQYDAKcoNMe5c
|
||||
i6mq2n9dBPghX9qCJkcswcEAk3BilySCvvnYRJFnEY3jSqFZfoUpPMjr+/4b78sF
|
||||
4F8qPwT27sHPH7H833ir8B86hlCGI0nCt1l4wD9CDWYKmKRsZT6oCtMLP6NdMMyn
|
||||
AMK4tPRYqlsP2fLtqQN1ODBPrfnraoNHtOVE784iBCD5dewICA5RIQG2i/d2+CGF
|
||||
+bahFqUXVCqHoxBz4AVvrRFL99VcP7P2iZyk6hDQ7fci7Xay8Wb/HutRxuqvF0aU
|
||||
bG6Enk6CCtNZHLwNPp4Hqft0Udvg2tG8okYwbEmoEO40nQsCSzRCpq5Uvzi+LX1k
|
||||
LykQ6+ECgYEA7x8vQoyOK60Q3LPpJFGDec2+XJPoesTfJTT6idaP7ukUL8p3FsUo
|
||||
9vtxRRfhSOdPoAINmrL0TyMekO2B6zXx0pmWVpMrFwZW6zMwZAnLp/w+3USpbGCy
|
||||
K12IIwvRYzTzKwoMTVAKTXm36b6oqr2La4bTdJR7REY6G374FrJb/H0CgYEA5CHk
|
||||
Ym0h7cf00fw9UEHRfzUZxmCfRWY6K8InOuHdLi+u4TiyXzs8x5s0e/DN/raNmTGx
|
||||
QO81UzuS3nKwc4n5QyXjVnhzR5DbbSACDwHtdnxZByL0D1KvPjtRF8F+rWXViXv2
|
||||
TM7UiOmn6R375FPSAPxeyMx8Womc3EnAAfLWGk8CgYEAv8I2WBv3dzcWqqbsdF+a
|
||||
G/fOjNdgO/PdLy1JLXiPfHwV4C1xSyVZMJd7wnjgBWLaC+sZldGk8kGrpXWSFlnw
|
||||
T38zfMIQcCp5Uax/RfpFA7XZhAAoDe2NdBFRtyknBXPU/dLVArsJSBAwWJa5FBNk
|
||||
1xoMQRVBtQLMXnh341utQNECgYEA4o1R2/ka16NaWmpPjXM/lD9skFgF84p4vFn8
|
||||
UXpaB3LtDdcbNH2Ed4mHToouWAR8jCUQLTcg0r53tRdaafMcKfXnVUka2nhdoHpH
|
||||
8RVt99u3IeIxU0I+q+OGPbw3jAV0UStcxpwj7q9zw4q2SuJ+y+HUUz7XQ6Yjs5Q9
|
||||
7PF2c/sCgYEAhdVn5gZ5FvYKrBi46t3pxPsWK476HmQEVHVi5+od7wg+araDelAe
|
||||
8QE8hc8qdZGbjdB/AHSPCeUxfO2vnpsCoSRs29o6pDvQuqvHYs+M53l5LEYeOjof
|
||||
t6J/DK5Pim2CAFjYFcZk8/Gyl5HjTw3PpdWxoPD5v2Xw3bbY57IIbm4=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
19
integration/fixtures/https/snitest.org.cert
Normal file
19
integration/fixtures/https/snitest.org.cert
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC/zCCAeegAwIBAgIJALAYHG/vGqWEMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
|
||||
BAMMC3NuaXRlc3Qub3JnMB4XDTE1MTEyMzIyMDU0NFoXDTI1MTEyMDIyMDU0NFow
|
||||
FjEUMBIGA1UEAwwLc25pdGVzdC5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC8b2Qv68Xnv4wgJ6HNupxABSUA5KXmv9g7pwwsFMSOK15o2qGFzx/x
|
||||
9loIi5pMIYIy4SVwJNrYUi772nCYMqSIVXlwct/CE70j2Jb2geIHu3jHbFWXruWb
|
||||
W1tGGUYzvnsOUziPE3rLWa/NObNYLLlUKJaxfHrxnpuKpQUsXzoLl25cJEVr4jg2
|
||||
ZITpdraxaBLisdlWY7EwwHBLu2nxH5Rn+nIjenFfdUwKF9s5dGy63tfBc8LX9yJk
|
||||
+kOwy1al/Wxa0DUb6rSt0QDCcD+rXnjk2zWPtsHz1btwtqM+FLtN5z0Lmnx7DF3C
|
||||
tCf1TMzduzZ6aeHk77zc664ZQun5cH33AgMBAAGjUDBOMB0GA1UdDgQWBBRn/nNz
|
||||
PUsmDKmKv3GGo3km5KKvUDAfBgNVHSMEGDAWgBRn/nNzPUsmDKmKv3GGo3km5KKv
|
||||
UDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBkuutIcbBdESgvNLLr
|
||||
k/8HUDuFm72lYHZFE+c76CxqYN52w02NCTiq1InoDUvqZXb/StATBwRRduTUPCj9
|
||||
KUkC7pOjAFxjzjExsHrtZSq01WinrxNI+qSKvI8jFngMHnwN1omTt7/D7nxeW5Of
|
||||
FJTkElnxtELAGHoIwZ+bKprnexefpn9UW84VJvJ2crSR63vBvdTrgsrEGW6kQj1I
|
||||
62laDpax4+x8t2h+sfG6uNIA1cFrG8Sk+O2Bi3ogB7Y/4e8r6WA23IRP+aSv0J2b
|
||||
k5fvuuXbIc979pQOoO03zG0S7Wpmpsw+9dQB9TOxGITOLfCZwEuIhnv+M9lLqCks
|
||||
7H2A
|
||||
-----END CERTIFICATE-----
|
||||
27
integration/fixtures/https/snitest.org.key
Normal file
27
integration/fixtures/https/snitest.org.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r/YO6cMLBTEjiteaNqh
|
||||
hc8f8fZaCIuaTCGCMuElcCTa2FIu+9pwmDKkiFV5cHLfwhO9I9iW9oHiB7t4x2xV
|
||||
l67lm1tbRhlGM757DlM4jxN6y1mvzTmzWCy5VCiWsXx68Z6biqUFLF86C5duXCRF
|
||||
a+I4NmSE6Xa2sWgS4rHZVmOxMMBwS7tp8R+UZ/pyI3pxX3VMChfbOXRsut7XwXPC
|
||||
1/ciZPpDsMtWpf1sWtA1G+q0rdEAwnA/q1545Ns1j7bB89W7cLajPhS7Tec9C5p8
|
||||
ewxdwrQn9UzM3bs2emnh5O+83OuuGULp+XB99wIDAQABAoIBAGOn9bByXQQnhZAr
|
||||
5aLMIn6pOdyzEBptM4q42fMmOJ2HyjJiDjKaTCbHRu5mBoBk6FrIP+iDVUo6jKad
|
||||
7BZSEjoYGlWiKzyU+97NWWmdX1D/kOzHGq1RzhTPyAHWtA4Bm0sEMFFa2AJbuGIt
|
||||
NfBYFtuva6MKVmsamuBETewdoLEnxzzDFcuOaxXRfTC/ikWcYyB4KEWA5fjroUQC
|
||||
Llo9/UTGTkh1Hynv9AXY6Qia/RbrIQjKveKCRj6PjxyE/qN9qfmngczz2pK0hRhL
|
||||
Z+K06y8G+Yj1I1zm5jNg1kakVQKoBsnaYkmIUBUSmWv6ERotedOWtOAMlOKa+0l2
|
||||
DS7Ou2ECgYEA91doi+3XrMVsgyTEm/ArzEyRUfM5dCSvBCRFhO7QQp2OYAbjJk5S
|
||||
pmdpqmwTsXNNMU+XNkWCLug5pk0PTJwP0mVLE2fLYqCCXoyaMltQ0Yk2gaun/RwE
|
||||
w5EfyMwOQakLFY/ODvduQfyNpaoWgFz4/WPNTVNCGs04LepSGKaFNy0CgYEAwwgV
|
||||
jKeFA+QZGooTInyk07ZlAbenEPu/c2y3UUFxclP0CjP2/VBOpz9B62vhzCKbjD1c
|
||||
/L3x1CKC4n4lbeyHi4vrF69LX9SHr1Jm0SUtyKeV3g0EAzIWI0HFhVUkMvtbibQ4
|
||||
HXrLVCJO77xetQ7RQszss1z9g3WotAAiBMiQgDMCgYBTLjoilOIrYFmV4Q+dwa95
|
||||
DWbxwHJZ9NxG8EvQ4N95B7OR578Matqwy6ZlgeM9kiErrDCWN9oIHGEG5HN4uCM6
|
||||
BoaxB/8GNCSj13Uj6kHLtfF2ulvMa1fOzUd7J+TDgC4SGkKaFewmlOCuDf1zPdEe
|
||||
pimtD4rzqIB0MJFbaOT0IQKBgDBPjlb7IB3ooLdMQJUoXwP6iGa2gXHZioEjCv3b
|
||||
wihZ13e3i5UQEYuoRcH1RUd1wyYoBSKuQnsT2WwVZ1wlXSYaELAbQgaI9NtfBA0G
|
||||
sqKjsKICg13vSECPiEgQ4Rin3vLra4MR6c/7d6Y2+RbMhtWPQYrkm/+2Y4XDCqo4
|
||||
rGK1AoGAOFZ3RVhuwXzFdKNe32LM1wm1eZ7waxjI4bQS2xUN/3C/uWS7A3LaSlc3
|
||||
eRG3DaVpez4DQVupZDHMgxJUYqqKynUj6GD1YiaxGROj3TYCu6e7OxyhalhCllSu
|
||||
w/X5M802XqzLjeec5zHoZDfknnAkgR9MsxZYmZPFaDyL6GOKUB8=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
5
integration/fixtures/simple_web.toml
Normal file
5
integration/fixtures/simple_web.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[web]
|
||||
|
||||
address = ":8080"
|
||||
110
integration/https_test.go
Normal file
110
integration/https_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
check "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// HTTPSSuite
|
||||
type HTTPSSuite struct{ BaseSuite }
|
||||
|
||||
// TestWithSNIConfigHandshake involves a client sending a SNI hostname of
|
||||
// "snitest.com", which happens to match the CN of 'snitest.com.crt'. The test
|
||||
// verifies that traefik presents the correct certificate.
|
||||
func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/https/https_sni.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
}
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
defer conn.Close()
|
||||
err = conn.Handshake()
|
||||
c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error"))
|
||||
|
||||
cs := conn.ConnectionState()
|
||||
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername"))
|
||||
}
|
||||
|
||||
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
|
||||
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
|
||||
// that traefik routes the requests to the expected backends.
|
||||
func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/https/https_sni.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
backend1 := startTestServer("9010", 204)
|
||||
backend2 := startTestServer("9020", 205)
|
||||
defer backend1.Close()
|
||||
defer backend2.Close()
|
||||
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
|
||||
tr1 := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
},
|
||||
}
|
||||
tr2 := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.org",
|
||||
},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr1}
|
||||
req, _ := http.NewRequest("GET", "https://127.0.0.1/", nil)
|
||||
req.Host = "snitest.com"
|
||||
req.Header.Set("Host", "snitest.com")
|
||||
req.Header.Set("Accept", "*/*")
|
||||
resp, err := client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
// Expected a 204 (from backend1)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 204)
|
||||
|
||||
client = &http.Client{Transport: tr2}
|
||||
req, _ = http.NewRequest("GET", "https://127.0.0.1/", nil)
|
||||
req.Host = "snitest.org"
|
||||
req.Header.Set("Host", "snitest.org")
|
||||
req.Header.Set("Accept", "*/*")
|
||||
resp, err = client.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
// Expected a 205 (from backend2)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 205)
|
||||
}
|
||||
|
||||
func startTestServer(port string, statusCode int) (ts *httptest.Server) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(statusCode)
|
||||
})
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:"+port)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ts = &httptest.Server{
|
||||
Listener: listener,
|
||||
Config: &http.Server{Handler: handler},
|
||||
}
|
||||
ts.Start()
|
||||
return
|
||||
}
|
||||
@@ -24,6 +24,7 @@ func Test(t *testing.T) {
|
||||
|
||||
func init() {
|
||||
check.Suite(&SimpleSuite{})
|
||||
check.Suite(&HTTPSSuite{})
|
||||
check.Suite(&FileSuite{})
|
||||
check.Suite(&DockerSuite{})
|
||||
check.Suite(&ConsulSuite{})
|
||||
@@ -32,9 +33,6 @@ func init() {
|
||||
|
||||
var traefikBinary = "../dist/traefik"
|
||||
|
||||
// SimpleSuite
|
||||
type SimpleSuite struct{ BaseSuite }
|
||||
|
||||
// File test suites
|
||||
type FileSuite struct{ BaseSuite }
|
||||
|
||||
@@ -44,17 +42,6 @@ func (s *FileSuite) SetUpSuite(c *check.C) {
|
||||
s.composeProject.Up()
|
||||
}
|
||||
|
||||
// Docker test suites
|
||||
type DockerSuite struct{ BaseSuite }
|
||||
|
||||
func (s *DockerSuite) SetUpSuite(c *check.C) {
|
||||
// Make sure we can speak to docker
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TearDownSuite(c *check.C) {
|
||||
// Clean the mess
|
||||
}
|
||||
|
||||
// Consul test suites (using libcompose)
|
||||
type ConsulSuite struct{ BaseSuite }
|
||||
|
||||
|
||||
@@ -10,18 +10,16 @@ import (
|
||||
)
|
||||
|
||||
func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "fixtures/consul/simple.toml")
|
||||
cmd := exec.Command(traefikBinary, "fixtures/marathon/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1/")
|
||||
|
||||
// Expected a 404 as we did not comfigure anything
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
|
||||
killErr := cmd.Process.Kill()
|
||||
c.Assert(killErr, checker.IsNil)
|
||||
}
|
||||
|
||||
195
kv.go
195
kv.go
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store/boltdb"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
"github.com/docker/libkv/store/zookeeper"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"errors"
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libkv/store"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KvProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
Prefix string
|
||||
Filename string
|
||||
StoreType store.Backend
|
||||
kvclient store.Store
|
||||
}
|
||||
|
||||
func NewConsulProvider(provider *ConsulProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
kvProvider.Filename = provider.Filename
|
||||
kvProvider.StoreType = store.CONSUL
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func NewEtcdProvider(provider *EtcdProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
kvProvider.Filename = provider.Filename
|
||||
kvProvider.StoreType = store.ETCD
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func NewZkProvider(provider *ZookepperProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
kvProvider.Filename = provider.Filename
|
||||
kvProvider.StoreType = store.ZK
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func NewBoltDbProvider(provider *BoltDbProvider) *KvProvider {
|
||||
kvProvider := new(KvProvider)
|
||||
kvProvider.Watch = provider.Watch
|
||||
kvProvider.Endpoint = provider.Endpoint
|
||||
kvProvider.Prefix = provider.Prefix
|
||||
kvProvider.Filename = provider.Filename
|
||||
kvProvider.StoreType = store.BOLTDB
|
||||
return kvProvider
|
||||
}
|
||||
|
||||
func (provider *KvProvider) provide(configurationChan chan<- configMessage) error {
|
||||
switch provider.StoreType {
|
||||
case store.CONSUL:
|
||||
consul.Register()
|
||||
case store.ETCD:
|
||||
etcd.Register()
|
||||
case store.ZK:
|
||||
zookeeper.Register()
|
||||
case store.BOLTDB:
|
||||
boltdb.Register()
|
||||
default:
|
||||
return errors.New("Invalid kv store: " + string(provider.StoreType))
|
||||
}
|
||||
kv, err := libkv.NewStore(
|
||||
provider.StoreType,
|
||||
[]string{provider.Endpoint},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := kv.List(""); err != nil {
|
||||
return err
|
||||
}
|
||||
provider.kvclient = kv
|
||||
if provider.Watch {
|
||||
stopCh := make(chan struct{})
|
||||
chanKeys, err := kv.WatchTree(provider.Prefix, stopCh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
<-chanKeys
|
||||
configuration := provider.loadConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{string(provider.StoreType), configuration}
|
||||
}
|
||||
defer close(stopCh)
|
||||
}
|
||||
}()
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
configurationChan <- configMessage{string(provider.StoreType), configuration}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *KvProvider) loadConfig() *Configuration {
|
||||
configuration := new(Configuration)
|
||||
templateObjects := struct {
|
||||
Prefix string
|
||||
}{
|
||||
provider.Prefix,
|
||||
}
|
||||
var KvFuncMap = template.FuncMap{
|
||||
"List": func(keys ...string) []string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keysPairs, err := provider.kvclient.List(joinedKeys)
|
||||
if err != nil {
|
||||
log.Error("Error getting keys: ", joinedKeys, err)
|
||||
return nil
|
||||
}
|
||||
directoryKeys := make(map[string]string)
|
||||
for _, key := range keysPairs {
|
||||
directory := strings.Split(strings.TrimPrefix(key.Key, strings.TrimPrefix(joinedKeys, "/")), "/")[0]
|
||||
directoryKeys[directory] = joinedKeys + directory
|
||||
}
|
||||
return fun.Values(directoryKeys).([]string)
|
||||
},
|
||||
"Get": func(keys ...string) string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||
if err != nil {
|
||||
log.Debug("Error getting key: ", joinedKeys, err)
|
||||
return ""
|
||||
} else if keyPair == nil {
|
||||
return ""
|
||||
}
|
||||
return string(keyPair.Value)
|
||||
},
|
||||
"Last": func(key string) string {
|
||||
splittedKey := strings.Split(key, "/")
|
||||
return splittedKey[len(splittedKey)-1]
|
||||
},
|
||||
}
|
||||
|
||||
tmpl := template.New(provider.Filename).Funcs(KvFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := Asset("providerTemplates/kv.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err := tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Error("Error with kv template:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
log.Error("Error creating kv configuration:", err)
|
||||
log.Error(buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
204
marathon.go
204
marathon.go
@@ -1,204 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
type MarathonProvider struct {
|
||||
Watch bool
|
||||
Endpoint string
|
||||
marathonClient marathon.Marathon
|
||||
Domain string
|
||||
Filename string
|
||||
NetworkInterface string
|
||||
}
|
||||
|
||||
func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
config.EventsInterface = provider.NetworkInterface
|
||||
client, err := marathon.NewClient(config)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return err
|
||||
}
|
||||
provider.marathonClient = client
|
||||
update := make(marathon.EventsChannel, 5)
|
||||
if provider.Watch {
|
||||
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
|
||||
log.Errorf("Failed to register for subscriptions, %s", err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
event := <-update
|
||||
log.Debug("Marathon event receveived", event)
|
||||
configuration := provider.loadMarathonConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{"marathon", configuration}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.loadMarathonConfig()
|
||||
configurationChan <- configMessage{"marathon", configuration}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
|
||||
var MarathonFuncMap = template.FuncMap{
|
||||
"getPort": func(task marathon.Task) string {
|
||||
for _, port := range task.Ports {
|
||||
return strconv.Itoa(port)
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"getHost": func(application marathon.Application) string {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.host" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return strings.Replace(application.ID, "/", "", 1)
|
||||
},
|
||||
"getWeight": func(application marathon.Application) string {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.weight" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return "0"
|
||||
},
|
||||
"getDomain": func(application marathon.Application) string {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.domain" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return provider.Domain
|
||||
},
|
||||
"getPrefixes": func(application marathon.Application) ([]string, error) {
|
||||
for key, value := range application.Labels {
|
||||
if key == "traefik.prefixes" {
|
||||
return strings.Split(value, ","), nil
|
||||
}
|
||||
}
|
||||
return []string{}, nil
|
||||
},
|
||||
"replace": func(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
},
|
||||
}
|
||||
configuration := new(Configuration)
|
||||
|
||||
applications, err := provider.marathonClient.Applications(nil)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := provider.marathonClient.AllTasks()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
//filter tasks
|
||||
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
||||
if len(task.Ports) == 0 {
|
||||
log.Debug("Filtering marathon task without port", task.AppID)
|
||||
return false
|
||||
}
|
||||
application := getApplication(task, applications.Apps)
|
||||
if application == nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(application.Labels["traefik.port"])
|
||||
if len(application.Ports) > 1 && err != nil {
|
||||
log.Debug("Filtering marathon task with more than 1 port and no traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
if application.Labels["traefik.enable"] == "false" {
|
||||
log.Debug("Filtering disabled marathon task", task.AppID)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, tasks.Tasks).([]marathon.Task)
|
||||
|
||||
//filter apps
|
||||
filteredApps := fun.Filter(func(app marathon.Application) bool {
|
||||
//get ports from app tasks
|
||||
if !fun.Exists(func(task marathon.Task) bool {
|
||||
if task.AppID == app.ID {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, filteredTasks) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, applications.Apps).([]marathon.Application)
|
||||
|
||||
templateObjects := struct {
|
||||
Applications []marathon.Application
|
||||
Tasks []marathon.Task
|
||||
Domain string
|
||||
}{
|
||||
filteredApps,
|
||||
filteredTasks,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
tmpl := template.New(provider.Filename).Funcs(MarathonFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
buf, err := Asset("providerTemplates/marathon.tmpl")
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
log.Error("Error reading file", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
log.Error("Error with marathon template:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
log.Error("Error creating marathon configuration:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return configuration
|
||||
}
|
||||
|
||||
func getApplication(task marathon.Task, apps []marathon.Application) *marathon.Application {
|
||||
for _, application := range apps {
|
||||
if application.ID == task.AppID {
|
||||
return &application
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
@@ -9,10 +6,12 @@ import (
|
||||
"github.com/mailgun/oxy/cbreaker"
|
||||
)
|
||||
|
||||
// CircuitBreaker holds the oxy circuit breaker.
|
||||
type CircuitBreaker struct {
|
||||
circuitBreaker *cbreaker.CircuitBreaker
|
||||
}
|
||||
|
||||
// NewCircuitBreaker returns a new CircuitBreaker.
|
||||
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker {
|
||||
circuitBreaker, _ := cbreaker.New(next, expression, options...)
|
||||
return &CircuitBreaker{circuitBreaker}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
@@ -16,7 +13,7 @@ type Logger struct {
|
||||
file *os.File
|
||||
}
|
||||
|
||||
// NewLogger returns a new Logger instance
|
||||
// NewLogger returns a new Logger instance.
|
||||
func NewLogger(file string) *Logger {
|
||||
if len(file) > 0 {
|
||||
fi, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
@@ -36,6 +33,7 @@ func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.Ha
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the logger (i.e. the file).
|
||||
func (l *Logger) Close() {
|
||||
l.file.Close()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
Copyright
|
||||
*/
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
@@ -11,10 +8,12 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Routes holds the gorilla mux routes (for the API & co).
|
||||
type Routes struct {
|
||||
router *mux.Router
|
||||
}
|
||||
|
||||
// NewRoutes return a Routes based on the given router.
|
||||
func NewRoutes(router *mux.Router) *Routes {
|
||||
return &Routes{router}
|
||||
}
|
||||
|
||||
52
middlewares/websocket.go
Normal file
52
middlewares/websocket.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/mailgun/oxy/roundrobin"
|
||||
)
|
||||
|
||||
// WebsocketUpgrader holds Websocket configuration.
|
||||
type WebsocketUpgrader struct {
|
||||
rr *roundrobin.RoundRobin
|
||||
}
|
||||
|
||||
// NewWebsocketUpgrader returns a new WebsocketUpgrader.
|
||||
func NewWebsocketUpgrader(rr *roundrobin.RoundRobin) *WebsocketUpgrader {
|
||||
wu := WebsocketUpgrader{
|
||||
rr: rr,
|
||||
}
|
||||
return &wu
|
||||
}
|
||||
|
||||
func (u *WebsocketUpgrader) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// If request is websocket, serve with golang websocket server to do protocol handshake
|
||||
if strings.Join(req.Header["Upgrade"], "") == "websocket" {
|
||||
start := time.Now().UTC()
|
||||
url, err := u.rr.NextServer()
|
||||
if err != nil {
|
||||
log.Errorf("Can't round robin in websocket middleware")
|
||||
return
|
||||
}
|
||||
log.Debugf("Websocket forward to %s", url.String())
|
||||
NewProxy(url).ServeHTTP(w, req)
|
||||
|
||||
if req.TLS != nil {
|
||||
log.Debugf("Round trip: %v, duration: %v tls:version: %x, tls:resume:%t, tls:csuite:%x, tls:server:%v",
|
||||
req.URL, time.Now().UTC().Sub(start),
|
||||
req.TLS.Version,
|
||||
req.TLS.DidResume,
|
||||
req.TLS.CipherSuite,
|
||||
req.TLS.ServerName)
|
||||
} else {
|
||||
log.Debugf("Round trip: %v, duration: %v",
|
||||
req.URL, time.Now().UTC().Sub(start))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
u.rr.ServeHTTP(w, req)
|
||||
}
|
||||
179
middlewares/websocketproxy.go
Normal file
179
middlewares/websocketproxy.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Original developpement made by https://github.com/koding/websocketproxy
|
||||
var (
|
||||
// DefaultUpgrader specifies the parameters for upgrading an HTTP
|
||||
// connection to a WebSocket connection.
|
||||
DefaultUpgrader = &websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
DefaultDialer = websocket.DefaultDialer
|
||||
)
|
||||
|
||||
// WebsocketProxy is an HTTP Handler that takes an incoming WebSocket
|
||||
// connection and proxies it to another server.
|
||||
type WebsocketProxy struct {
|
||||
// Backend returns the backend URL which the proxy uses to reverse proxy
|
||||
// the incoming WebSocket connection. Request is the initial incoming and
|
||||
// unmodified request.
|
||||
Backend func(*http.Request) *url.URL
|
||||
|
||||
// Upgrader specifies the parameters for upgrading a incoming HTTP
|
||||
// connection to a WebSocket connection. If nil, DefaultUpgrader is used.
|
||||
Upgrader *websocket.Upgrader
|
||||
|
||||
// Dialer contains options for connecting to the backend WebSocket server.
|
||||
// If nil, DefaultDialer is used.
|
||||
Dialer *websocket.Dialer
|
||||
}
|
||||
|
||||
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
||||
// request to the given target.
|
||||
func ProxyHandler(target *url.URL) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
NewProxy(target).ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// NewProxy returns a new Websocket reverse proxy that rewrites the
|
||||
// URL's to the scheme, host and base path provider in target.
|
||||
func NewProxy(target *url.URL) *WebsocketProxy {
|
||||
backend := func(r *http.Request) *url.URL {
|
||||
// Shallow copy
|
||||
u := *target
|
||||
u.Fragment = r.URL.Fragment
|
||||
u.Path = r.URL.Path
|
||||
u.RawQuery = r.URL.RawQuery
|
||||
rurl := u.String()
|
||||
if strings.HasPrefix(rurl, "http") {
|
||||
u.Scheme = "ws"
|
||||
}
|
||||
if strings.HasPrefix(rurl, "https") {
|
||||
u.Scheme = "wss"
|
||||
}
|
||||
return &u
|
||||
}
|
||||
return &WebsocketProxy{Backend: backend}
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if w.Backend == nil {
|
||||
log.Errorf("Websocketproxy: backend function is not defined")
|
||||
http.Error(rw, "Backend not found", http.StatusInternalServerError)
|
||||
http.NotFound(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := w.Backend(req)
|
||||
if backendURL == nil {
|
||||
log.Errorf("Websocketproxy: backend URL is nil")
|
||||
http.Error(rw, "Backend URL is nil", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
dialer := w.Dialer
|
||||
if w.Dialer == nil {
|
||||
dialer = DefaultDialer
|
||||
}
|
||||
|
||||
// Pass headers from the incoming request to the dialer to forward them to
|
||||
// the final destinations.
|
||||
requestHeader := http.Header{}
|
||||
requestHeader.Add("Origin", req.Header.Get("Origin"))
|
||||
for _, prot := range req.Header[http.CanonicalHeaderKey("Sec-WebSocket-Protocol")] {
|
||||
requestHeader.Add("Sec-WebSocket-Protocol", prot)
|
||||
}
|
||||
for _, cookie := range req.Header[http.CanonicalHeaderKey("Cookie")] {
|
||||
requestHeader.Add("Cookie", cookie)
|
||||
}
|
||||
for _, auth := range req.Header[http.CanonicalHeaderKey("Authorization")] {
|
||||
requestHeader.Add("Authorization", auth)
|
||||
}
|
||||
|
||||
// Pass X-Forwarded-For headers too, code below is a part of
|
||||
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
|
||||
// for more information
|
||||
// TODO: use RFC7239 http://tools.ietf.org/html/rfc7239
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
requestHeader.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
|
||||
// Set the originating protocol of the incoming HTTP request. The SSL might
|
||||
// be terminated on our site and because we doing proxy adding this would
|
||||
// be helpful for applications on the backend.
|
||||
requestHeader.Set("X-Forwarded-Proto", "http")
|
||||
if req.TLS != nil {
|
||||
requestHeader.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
|
||||
//frontend Origin != backend Origin
|
||||
requestHeader.Del("Origin")
|
||||
|
||||
// Connect to the backend URL, also pass the headers we get from the requst
|
||||
// together with the Forwarded headers we prepared above.
|
||||
// TODO: support multiplexing on the same backend connection instead of
|
||||
// opening a new TCP connection time for each request. This should be
|
||||
// optional:
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01
|
||||
connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader)
|
||||
if err != nil {
|
||||
log.Errorf("Websocketproxy: couldn't dial to remote backend url %s, %s, %+v", backendURL.String(), err, resp)
|
||||
http.Error(rw, "Remote backend unreachable", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer connBackend.Close()
|
||||
|
||||
upgrader := w.Upgrader
|
||||
if w.Upgrader == nil {
|
||||
upgrader = DefaultUpgrader
|
||||
}
|
||||
|
||||
// Only pass those headers to the upgrader.
|
||||
upgradeHeader := http.Header{}
|
||||
upgradeHeader.Set("Sec-WebSocket-Protocol",
|
||||
resp.Header.Get(http.CanonicalHeaderKey("Sec-WebSocket-Protocol")))
|
||||
upgradeHeader.Set("Set-Cookie",
|
||||
resp.Header.Get(http.CanonicalHeaderKey("Set-Cookie")))
|
||||
|
||||
// Now upgrade the existing incoming request to a WebSocket connection.
|
||||
// Also pass the header that we gathered from the Dial handshake.
|
||||
connPub, err := upgrader.Upgrade(rw, req, upgradeHeader)
|
||||
if err != nil {
|
||||
log.Errorf("Websocketproxy: couldn't upgrade %s", err)
|
||||
http.NotFound(rw, req)
|
||||
return
|
||||
}
|
||||
defer connPub.Close()
|
||||
|
||||
errc := make(chan error, 2)
|
||||
cp := func(dst io.Writer, src io.Reader) {
|
||||
_, err := io.Copy(dst, src)
|
||||
errc <- err
|
||||
}
|
||||
|
||||
// Start our proxy now, everything is ready...
|
||||
go cp(connBackend.UnderlyingConn(), connPub.UnderlyingConn())
|
||||
go cp(connPub.UnderlyingConn(), connBackend.UnderlyingConn())
|
||||
<-errc
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package main
|
||||
|
||||
type Provider interface {
|
||||
Provide(configurationChan chan<- configMessage) error
|
||||
}
|
||||
20
provider/boltdb.go
Normal file
20
provider/boltdb.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/boltdb"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// BoltDb holds configurations of the BoltDb provider.
|
||||
type BoltDb struct {
|
||||
Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.StoreType = store.BOLTDB
|
||||
boltdb.Register()
|
||||
return provider.provide(configurationChan)
|
||||
}
|
||||
20
provider/consul.go
Normal file
20
provider/consul.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Consul holds configurations of the Consul provider.
|
||||
type Consul struct {
|
||||
Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.StoreType = store.CONSUL
|
||||
consul.Register()
|
||||
return provider.provide(configurationChan)
|
||||
}
|
||||
272
provider/docker.go
Normal file
272
provider/docker.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
// Docker holds configurations of the Docker provider.
|
||||
type Docker struct {
|
||||
baseProvider
|
||||
Endpoint string
|
||||
Domain string
|
||||
TLS *DockerTLS
|
||||
}
|
||||
|
||||
// DockerTLS holds TLS specific configurations
|
||||
type DockerTLS struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
|
||||
var dockerClient *docker.Client
|
||||
var err error
|
||||
|
||||
if provider.TLS != nil {
|
||||
dockerClient, err = docker.NewTLSClient(provider.Endpoint,
|
||||
provider.TLS.Cert, provider.TLS.Key, provider.TLS.CA)
|
||||
if err == nil {
|
||||
dockerClient.TLSConfig.InsecureSkipVerify = provider.TLS.InsecureSkipVerify
|
||||
}
|
||||
} else {
|
||||
dockerClient, err = docker.NewClient(provider.Endpoint)
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for docker, error: %s", err)
|
||||
return err
|
||||
}
|
||||
err = dockerClient.Ping()
|
||||
if err != nil {
|
||||
log.Errorf("Docker connection error %+v", err)
|
||||
return err
|
||||
}
|
||||
log.Debug("Docker connection established")
|
||||
if provider.Watch {
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
dockerClient.AddEventListener(dockerEvents)
|
||||
log.Debug("Docker listening")
|
||||
go func() {
|
||||
operation := func() error {
|
||||
for {
|
||||
event := <-dockerEvents
|
||||
if event == nil {
|
||||
return errors.New("Docker event nil")
|
||||
// log.Fatalf("Docker connection error")
|
||||
}
|
||||
if event.Status == "start" || event.Status == "die" {
|
||||
log.Debugf("Docker event receveived %+v", event)
|
||||
configuration := provider.loadDockerConfig(listContainers(dockerClient))
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
configuration := provider.loadDockerConfig(listContainers(dockerClient))
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Docker) loadDockerConfig(containersInspected []docker.Container) *types.Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getFrontendValue": provider.getFrontendValue,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"replace": replace,
|
||||
}
|
||||
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(containerFilter, containersInspected).([]docker.Container)
|
||||
|
||||
frontends := map[string][]docker.Container{}
|
||||
for _, container := range filteredContainers {
|
||||
frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container)
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Containers []docker.Container
|
||||
Frontends map[string][]docker.Container
|
||||
Domain string
|
||||
}{
|
||||
filteredContainers,
|
||||
frontends,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func containerFilter(container docker.Container) bool {
|
||||
if len(container.NetworkSettings.Ports) == 0 {
|
||||
log.Debugf("Filtering container without port %s", container.Name)
|
||||
return false
|
||||
}
|
||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
if container.Config.Labels["traefik.enable"] == "false" {
|
||||
log.Debugf("Filtering disabled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
labels, err := getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"})
|
||||
if len(labels) != 0 && err != nil {
|
||||
log.Debugf("Filtering bad labeled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) getFrontendName(container docker.Container) string {
|
||||
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
|
||||
frontendName := fmt.Sprintf("%s-%s", provider.getFrontendRule(container), provider.getFrontendValue(container))
|
||||
frontendName = strings.Replace(frontendName, "[", "", -1)
|
||||
frontendName = strings.Replace(frontendName, "]", "", -1)
|
||||
|
||||
return strings.Replace(frontendName, ".", "-", -1)
|
||||
}
|
||||
|
||||
// GetFrontendValue returns the frontend value for the specified container, using
|
||||
// it's label. It returns a default one if the label is not present.
|
||||
func (provider *Docker) getFrontendValue(container docker.Container) string {
|
||||
if label, err := getLabel(container, "traefik.frontend.value"); err == nil {
|
||||
return label
|
||||
}
|
||||
return getEscapedName(container.Name) + "." + provider.Domain
|
||||
}
|
||||
|
||||
// GetFrontendRule returns the frontend rule for the specified container, using
|
||||
// it's label. It returns a default one (Host) if the label is not present.
|
||||
func (provider *Docker) getFrontendRule(container docker.Container) string {
|
||||
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host"
|
||||
}
|
||||
|
||||
func (provider *Docker) getBackend(container docker.Container) string {
|
||||
if label, err := getLabel(container, "traefik.backend"); err == nil {
|
||||
return label
|
||||
}
|
||||
return getEscapedName(container.Name)
|
||||
}
|
||||
|
||||
func (provider *Docker) getPort(container docker.Container) string {
|
||||
if label, err := getLabel(container, "traefik.port"); err == nil {
|
||||
return label
|
||||
}
|
||||
for key := range container.NetworkSettings.Ports {
|
||||
return key.Port()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Docker) getWeight(container docker.Container) string {
|
||||
if label, err := getLabel(container, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Docker) getDomain(container docker.Container) string {
|
||||
if label, err := getLabel(container, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Docker) getProtocol(container docker.Container) string {
|
||||
if label, err := getLabel(container, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (provider *Docker) getPassHostHeader(container docker.Container) string {
|
||||
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func getLabel(container docker.Container, label string) (string, error) {
|
||||
for key, value := range container.Config.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func getLabels(container docker.Container, labels []string) (map[string]string, error) {
|
||||
var globalErr error
|
||||
foundLabels := map[string]string{}
|
||||
for _, label := range labels {
|
||||
foundLabel, err := getLabel(container, label)
|
||||
// Error out only if one of them is defined.
|
||||
if err != nil {
|
||||
globalErr = errors.New("Label not found: " + label)
|
||||
continue
|
||||
}
|
||||
foundLabels[label] = foundLabel
|
||||
|
||||
}
|
||||
return foundLabels, globalErr
|
||||
}
|
||||
|
||||
func listContainers(dockerClient *docker.Client) []docker.Container {
|
||||
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
|
||||
containersInspected := []docker.Container{}
|
||||
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
containerInspected, _ := dockerClient.InspectContainer(container.ID)
|
||||
containersInspected = append(containersInspected, *containerInspected)
|
||||
}
|
||||
return containersInspected
|
||||
}
|
||||
788
provider/docker_test.go
Normal file
788
provider/docker_test.go
Normal file
@@ -0,0 +1,788 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func TestDockerGetFrontendName(t *testing.T) {
|
||||
provider := &Docker{
|
||||
Domain: "docker.localhost",
|
||||
}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "Host-foo-docker-localhost",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "bar",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.rule": "Header",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "Header-bar-docker-localhost",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.value": "foo.bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "Host-foo-bar",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.value": "foo.bar",
|
||||
"traefik.frontend.rule": "Header",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "Header-foo-bar",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.value": "[foo.bar]",
|
||||
"traefik.frontend.rule": "Header",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "Header-foo-bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getFrontendName(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetFrontendValue(t *testing.T) {
|
||||
provider := &Docker{
|
||||
Domain: "docker.localhost",
|
||||
}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "foo.docker.localhost",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "bar",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "bar.docker.localhost",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.value": "foo.bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "foo.bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getFrontendValue(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetFrontendRule(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "Host",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.rule": "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getFrontendRule(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetBackend(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "bar",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "bar",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.backend": "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "foobar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getBackend(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetPort(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "bar",
|
||||
Config: &docker.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "80",
|
||||
},
|
||||
// FIXME handle this better..
|
||||
// {
|
||||
// container: docker.Container{
|
||||
// Name: "bar",
|
||||
// Config: &docker.Config{},
|
||||
// NetworkSettings: &docker.NetworkSettings{
|
||||
// Ports: map[docker.Port][]docker.PortBinding{
|
||||
// "80/tcp": []docker.PortBinding{},
|
||||
// "443/tcp": []docker.PortBinding{},
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// expected: "80",
|
||||
// },
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "8080",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "8080",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getPort(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetWeight(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.weight": "10",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "10",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getWeight(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetDomain(t *testing.T) {
|
||||
provider := &Docker{
|
||||
Domain: "docker.localhost",
|
||||
}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "docker.localhost",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.domain": "foo.bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "foo.bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getDomain(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetProtocol(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "http",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.protocol": "https",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "https",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getProtocol(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetPassHostHeader(t *testing.T) {
|
||||
provider := &Docker{}
|
||||
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "foo",
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "false",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Name: "test",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.passHostHeader": "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "true",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := provider.getPassHostHeader(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %q, got %q", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetLabel(t *testing.T) {
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expected: "Label not found:",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
label, err := getLabel(e.container, "foo")
|
||||
if e.expected != "" {
|
||||
if err == nil || !strings.Contains(err.Error(), e.expected) {
|
||||
t.Fatalf("expected an error with %q, got %v", e.expected, err)
|
||||
}
|
||||
} else {
|
||||
if label != "bar" {
|
||||
t.Fatalf("expected label 'bar', got %s", label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerGetLabels(t *testing.T) {
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expectedLabels map[string]string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{},
|
||||
},
|
||||
expectedLabels: map[string]string{},
|
||||
expectedError: "Label not found:",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"foo": "fooz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"foo": "fooz",
|
||||
},
|
||||
expectedError: "Label not found: bar",
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"foo": "fooz",
|
||||
"bar": "barz",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedLabels: map[string]string{
|
||||
"foo": "fooz",
|
||||
"bar": "barz",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
labels, err := getLabels(e.container, []string{"foo", "bar"})
|
||||
if !reflect.DeepEqual(labels, e.expectedLabels) {
|
||||
t.Fatalf("expect %v, got %v", e.expectedLabels, labels)
|
||||
}
|
||||
if e.expectedError != "" {
|
||||
if err == nil || !strings.Contains(err.Error(), e.expectedError) {
|
||||
t.Fatalf("expected an error with %q, got %v", e.expectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerTraefikFilter(t *testing.T) {
|
||||
containers := []struct {
|
||||
container docker.Container
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.enable": "false",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.rule": "Host",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.value": "foo.bar",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
"443/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "80",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
"443/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.enable": "true",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.enable": "anything",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
container: docker.Container{
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.rule": "Host",
|
||||
"traefik.frontend.value": "foo.bar",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, e := range containers {
|
||||
actual := containerFilter(e.container)
|
||||
if actual != e.expected {
|
||||
t.Fatalf("expected %v, got %v", e.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerLoadDockerConfig(t *testing.T) {
|
||||
cases := []struct {
|
||||
containers []docker.Container
|
||||
expectedFrontends map[string]*types.Frontend
|
||||
expectedBackends map[string]*types.Backend
|
||||
}{
|
||||
{
|
||||
containers: []docker.Container{},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
containers: []docker.Container{
|
||||
{
|
||||
Name: "test",
|
||||
Config: &docker.Config{},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
IPAddress: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`"frontend-Host-test-docker-localhost"`: {
|
||||
Backend: "backend-test",
|
||||
Routes: map[string]types.Route{
|
||||
`"route-frontend-Host-test-docker-localhost"`: {
|
||||
Rule: "Host",
|
||||
Value: "test.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-test": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containers: []docker.Container{
|
||||
{
|
||||
Name: "test1",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.backend": "foobar",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
IPAddress: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test2",
|
||||
Config: &docker.Config{
|
||||
Labels: map[string]string{
|
||||
"traefik.backend": "foobar",
|
||||
},
|
||||
},
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
Ports: map[docker.Port][]docker.PortBinding{
|
||||
"80/tcp": {},
|
||||
},
|
||||
IPAddress: "127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`"frontend-Host-test1-docker-localhost"`: {
|
||||
Backend: "backend-foobar",
|
||||
Routes: map[string]types.Route{
|
||||
`"route-frontend-Host-test1-docker-localhost"`: {
|
||||
Rule: "Host",
|
||||
Value: "test1.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
`"frontend-Host-test2-docker-localhost"`: {
|
||||
Backend: "backend-foobar",
|
||||
Routes: map[string]types.Route{
|
||||
`"route-frontend-Host-test2-docker-localhost"`: {
|
||||
Rule: "Host",
|
||||
Value: "test2.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-foobar": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test1": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
},
|
||||
"server-test2": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provider := &Docker{
|
||||
Domain: "docker.localhost",
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actualConfig := provider.loadDockerConfig(c.containers)
|
||||
// Compare backends
|
||||
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
|
||||
}
|
||||
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
|
||||
}
|
||||
}
|
||||
}
|
||||
20
provider/etcd.go
Normal file
20
provider/etcd.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Etcd holds configurations of the Etcd provider.
|
||||
type Etcd struct {
|
||||
Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.StoreType = store.ETCD
|
||||
etcd.Register()
|
||||
return provider.provide(configurationChan)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package provider
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -7,15 +7,18 @@ import (
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
type FileProvider struct {
|
||||
Watch bool
|
||||
Filename string
|
||||
// File holds configurations of the File provider.
|
||||
type File struct {
|
||||
baseProvider
|
||||
}
|
||||
|
||||
func (provider *FileProvider) Provide(configurationChan chan<- configMessage) error {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("Error creating file watcher", err)
|
||||
@@ -38,9 +41,12 @@ func (provider *FileProvider) Provide(configurationChan chan<- configMessage) er
|
||||
case event := <-watcher.Events:
|
||||
if strings.Contains(event.Name, file.Name()) {
|
||||
log.Debug("File event:", event)
|
||||
configuration := provider.LoadFileConfig(file.Name())
|
||||
configuration := provider.loadFileConfig(file.Name())
|
||||
if configuration != nil {
|
||||
configurationChan <- configMessage{"file", configuration}
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "file",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
case error := <-watcher.Errors:
|
||||
@@ -55,13 +61,16 @@ func (provider *FileProvider) Provide(configurationChan chan<- configMessage) er
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.LoadFileConfig(file.Name())
|
||||
configurationChan <- configMessage{"file", configuration}
|
||||
configuration := provider.loadFileConfig(file.Name())
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "file",
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *FileProvider) LoadFileConfig(filename string) *Configuration {
|
||||
configuration := new(Configuration)
|
||||
func (provider *File) loadFileConfig(filename string) *types.Configuration {
|
||||
configuration := new(types.Configuration)
|
||||
if _, err := toml.DecodeFile(filename, configuration); err != nil {
|
||||
log.Error("Error reading file:", err)
|
||||
return nil
|
||||
1
provider/file_test.go
Normal file
1
provider/file_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package provider
|
||||
118
provider/kv.go
Normal file
118
provider/kv.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Package provider holds the different provider implementation.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Kv holds common configurations of key-value providers.
|
||||
type Kv struct {
|
||||
baseProvider
|
||||
Endpoint string
|
||||
Prefix string
|
||||
StoreType store.Backend
|
||||
kvclient store.Store
|
||||
}
|
||||
|
||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
kv, err := libkv.NewStore(
|
||||
provider.StoreType,
|
||||
[]string{provider.Endpoint},
|
||||
&store.Config{
|
||||
ConnectionTimeout: 30 * time.Second,
|
||||
Bucket: "traefik",
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := kv.List(""); err != nil {
|
||||
return err
|
||||
}
|
||||
provider.kvclient = kv
|
||||
if provider.Watch {
|
||||
stopCh := make(chan struct{})
|
||||
chanKeys, err := kv.WatchTree(provider.Prefix, stopCh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
<-chanKeys
|
||||
configuration := provider.loadConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(provider.StoreType),
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
defer close(stopCh)
|
||||
}
|
||||
}()
|
||||
}
|
||||
configuration := provider.loadConfig()
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: string(provider.StoreType),
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Kv) loadConfig() *types.Configuration {
|
||||
templateObjects := struct {
|
||||
Prefix string
|
||||
}{
|
||||
provider.Prefix,
|
||||
}
|
||||
var KvFuncMap = template.FuncMap{
|
||||
"List": provider.list,
|
||||
"Get": provider.get,
|
||||
"Last": provider.last,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *Kv) list(keys ...string) []string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keysPairs, err := provider.kvclient.List(joinedKeys)
|
||||
if err != nil {
|
||||
log.Error("Error getting keys: ", joinedKeys, err)
|
||||
return nil
|
||||
}
|
||||
directoryKeys := make(map[string]string)
|
||||
for _, key := range keysPairs {
|
||||
directory := strings.Split(strings.TrimPrefix(key.Key, strings.TrimPrefix(joinedKeys, "/")), "/")[0]
|
||||
directoryKeys[directory] = joinedKeys + directory
|
||||
}
|
||||
return fun.Values(directoryKeys).([]string)
|
||||
}
|
||||
|
||||
func (provider *Kv) get(keys ...string) string {
|
||||
joinedKeys := strings.Join(keys, "")
|
||||
keyPair, err := provider.kvclient.Get(joinedKeys)
|
||||
if err != nil {
|
||||
log.Error("Error getting key: ", joinedKeys, err)
|
||||
return ""
|
||||
} else if keyPair == nil {
|
||||
return ""
|
||||
}
|
||||
return string(keyPair.Value)
|
||||
}
|
||||
|
||||
func (provider *Kv) last(key string) string {
|
||||
splittedKey := strings.Split(key, "/")
|
||||
return splittedKey[len(splittedKey)-1]
|
||||
}
|
||||
312
provider/kv_test.go
Normal file
312
provider/kv_test.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/libkv/store"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func TestKvList(t *testing.T) {
|
||||
cases := []struct {
|
||||
provider *Kv
|
||||
keys []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{"traefik"},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
keys: []string{"bar"},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
keys: []string{"foo"},
|
||||
expected: []string{"foo"},
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo/baz/1",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: "foo/baz/2",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
{
|
||||
Key: "foo/baz/biz/1",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
keys: []string{"foo", "/baz/"},
|
||||
expected: []string{"foo/baz/biz", "foo/baz/1", "foo/baz/2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actual := c.provider.list(c.keys...)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(c.expected)
|
||||
if !reflect.DeepEqual(actual, c.expected) {
|
||||
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
|
||||
}
|
||||
}
|
||||
|
||||
// Error case
|
||||
provider := &Kv{
|
||||
kvclient: &Mock{
|
||||
Error: true,
|
||||
},
|
||||
}
|
||||
actual := provider.list("anything")
|
||||
if actual != nil {
|
||||
t.Fatalf("Should have return nil, got %v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKvGet(t *testing.T) {
|
||||
cases := []struct {
|
||||
provider *Kv
|
||||
keys []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{},
|
||||
},
|
||||
keys: []string{"traefik"},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
keys: []string{"bar"},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
keys: []string{"foo"},
|
||||
expected: "bar",
|
||||
},
|
||||
{
|
||||
provider: &Kv{
|
||||
kvclient: &Mock{
|
||||
KVPairs: []*store.KVPair{
|
||||
{
|
||||
Key: "foo/baz/1",
|
||||
Value: []byte("bar1"),
|
||||
},
|
||||
{
|
||||
Key: "foo/baz/2",
|
||||
Value: []byte("bar2"),
|
||||
},
|
||||
{
|
||||
Key: "foo/baz/biz/1",
|
||||
Value: []byte("bar3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
keys: []string{"foo", "/baz/", "2"},
|
||||
expected: "bar2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actual := c.provider.get(c.keys...)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %v, got %v for %v and %v", c.expected, actual, c.keys, c.provider)
|
||||
}
|
||||
}
|
||||
|
||||
// Error case
|
||||
provider := &Kv{
|
||||
kvclient: &Mock{
|
||||
Error: true,
|
||||
},
|
||||
}
|
||||
actual := provider.get("anything")
|
||||
if actual != "" {
|
||||
t.Fatalf("Should have return nil, got %v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKvLast(t *testing.T) {
|
||||
cases := []struct {
|
||||
key string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
key: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
key: "foo",
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
key: "foo/bar",
|
||||
expected: "bar",
|
||||
},
|
||||
{
|
||||
key: "foo/bar/baz",
|
||||
expected: "baz",
|
||||
},
|
||||
// FIXME is this wanted ?
|
||||
{
|
||||
key: "foo/bar/",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
provider := &Kv{}
|
||||
for _, c := range cases {
|
||||
actual := provider.last(c.key)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %s, got %s", c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extremely limited mock store so we can test initialization
|
||||
type Mock struct {
|
||||
Error bool
|
||||
KVPairs []*store.KVPair
|
||||
}
|
||||
|
||||
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
|
||||
return errors.New("Put not supported")
|
||||
}
|
||||
|
||||
func (s *Mock) Get(key string) (*store.KVPair, error) {
|
||||
if s.Error {
|
||||
return nil, errors.New("Error")
|
||||
}
|
||||
for _, kvPair := range s.KVPairs {
|
||||
if kvPair.Key == key {
|
||||
return kvPair, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Mock) Delete(key string) error {
|
||||
return errors.New("Delete not supported")
|
||||
}
|
||||
|
||||
// Exists mock
|
||||
func (s *Mock) Exists(key string) (bool, error) {
|
||||
return false, errors.New("Exists not supported")
|
||||
}
|
||||
|
||||
// Watch mock
|
||||
func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
|
||||
return nil, errors.New("Watch not supported")
|
||||
}
|
||||
|
||||
// WatchTree mock
|
||||
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
|
||||
return nil, errors.New("WatchTree not supported")
|
||||
}
|
||||
|
||||
// NewLock mock
|
||||
func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
|
||||
return nil, errors.New("NewLock not supported")
|
||||
}
|
||||
|
||||
// List mock
|
||||
func (s *Mock) List(prefix string) ([]*store.KVPair, error) {
|
||||
if s.Error {
|
||||
return nil, errors.New("Error")
|
||||
}
|
||||
kv := []*store.KVPair{}
|
||||
for _, kvPair := range s.KVPairs {
|
||||
if strings.HasPrefix(kvPair.Key, prefix) {
|
||||
kv = append(kv, kvPair)
|
||||
}
|
||||
}
|
||||
return kv, nil
|
||||
}
|
||||
|
||||
// DeleteTree mock
|
||||
func (s *Mock) DeleteTree(prefix string) error {
|
||||
return errors.New("DeleteTree not supported")
|
||||
}
|
||||
|
||||
// AtomicPut mock
|
||||
func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
|
||||
return false, nil, errors.New("AtomicPut not supported")
|
||||
}
|
||||
|
||||
// AtomicDelete mock
|
||||
func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
|
||||
return false, errors.New("AtomicDelete not supported")
|
||||
}
|
||||
|
||||
// Close mock
|
||||
func (s *Mock) Close() {
|
||||
return
|
||||
}
|
||||
304
provider/marathon.go
Normal file
304
provider/marathon.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
// Marathon holds configuration of the Marathon provider.
|
||||
type Marathon struct {
|
||||
baseProvider
|
||||
Endpoint string
|
||||
Domain string
|
||||
NetworkInterface string
|
||||
Basic *MarathonBasic
|
||||
marathonClient lightMarathonClient
|
||||
}
|
||||
|
||||
// MarathonBasic holds basic authentication specific configurations
|
||||
type MarathonBasic struct {
|
||||
HTTPBasicAuthUser string
|
||||
HTTPBasicPassword string
|
||||
}
|
||||
|
||||
type lightMarathonClient interface {
|
||||
Applications(url.Values) (*marathon.Applications, error)
|
||||
AllTasks(v url.Values) (*marathon.Tasks, error)
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
config := marathon.NewDefaultConfig()
|
||||
config.URL = provider.Endpoint
|
||||
config.EventsInterface = provider.NetworkInterface
|
||||
if provider.Basic != nil {
|
||||
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
|
||||
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
|
||||
}
|
||||
client, err := marathon.NewClient(config)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return err
|
||||
}
|
||||
provider.marathonClient = client
|
||||
update := make(marathon.EventsChannel, 5)
|
||||
if provider.Watch {
|
||||
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
|
||||
log.Errorf("Failed to register for subscriptions, %s", err)
|
||||
} else {
|
||||
go func() {
|
||||
for {
|
||||
event := <-update
|
||||
log.Debug("Marathon event receveived", event)
|
||||
configuration := provider.loadMarathonConfig()
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "marathon",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
configuration := provider.loadMarathonConfig()
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "marathon",
|
||||
Configuration: configuration,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Marathon) loadMarathonConfig() *types.Configuration {
|
||||
var MarathonFuncMap = template.FuncMap{
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getFrontendValue": provider.getFrontendValue,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"replace": replace,
|
||||
}
|
||||
|
||||
applications, err := provider.marathonClient.Applications(nil)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := provider.marathonClient.AllTasks((url.Values{"status": []string{"running"}}))
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create a client for marathon, error: %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
//filter tasks
|
||||
filteredTasks := fun.Filter(func(task marathon.Task) bool {
|
||||
return taskFilter(task, applications)
|
||||
}, tasks.Tasks).([]marathon.Task)
|
||||
|
||||
//filter apps
|
||||
filteredApps := fun.Filter(func(app marathon.Application) bool {
|
||||
return applicationFilter(app, filteredTasks)
|
||||
}, applications.Apps).([]marathon.Application)
|
||||
|
||||
templateObjects := struct {
|
||||
Applications []marathon.Application
|
||||
Tasks []marathon.Task
|
||||
Domain string
|
||||
}{
|
||||
filteredApps,
|
||||
filteredTasks,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
configuration, err := provider.getConfiguration("templates/marathon.tmpl", MarathonFuncMap, templateObjects)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return configuration
|
||||
}
|
||||
|
||||
func taskFilter(task marathon.Task, applications *marathon.Applications) bool {
|
||||
if len(task.Ports) == 0 {
|
||||
log.Debug("Filtering marathon task without port %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
application, err := getApplication(task, applications.Apps)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
if application.Labels["traefik.enable"] == "false" {
|
||||
log.Debugf("Filtering disabled marathon task %s", task.AppID)
|
||||
return false
|
||||
}
|
||||
|
||||
//filter indeterminable task port
|
||||
portIndexLabel := application.Labels["traefik.portIndex"]
|
||||
portValueLabel := application.Labels["traefik.port"]
|
||||
if portIndexLabel != "" && portValueLabel != "" {
|
||||
log.Debugf("Filtering marathon task %s specifying both traefik.portIndex and traefik.port labels", task.AppID)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel == "" && portValueLabel == "" && len(application.Ports) > 1 {
|
||||
log.Debugf("Filtering marathon task %s with more than 1 port and no traefik.portIndex or traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
if portIndexLabel != "" {
|
||||
index, err := strconv.Atoi(application.Labels["traefik.portIndex"])
|
||||
if err != nil || index < 0 || index > len(application.Ports)-1 {
|
||||
log.Debugf("Filtering marathon task %s with unexpected value for traefik.portIndex label", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if portValueLabel != "" {
|
||||
port, err := strconv.Atoi(application.Labels["traefik.port"])
|
||||
if err != nil {
|
||||
log.Debugf("Filtering marathon task %s with unexpected value for traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
|
||||
var foundPort bool
|
||||
for _, exposedPort := range task.Ports {
|
||||
if port == exposedPort {
|
||||
foundPort = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundPort {
|
||||
log.Debugf("Filtering marathon task %s without a matching port for traefik.port label", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//filter healthchecks
|
||||
if application.HasHealthChecks() {
|
||||
if task.HasHealthCheckResults() {
|
||||
for _, healthcheck := range task.HealthCheckResult {
|
||||
// found one bad healthcheck, return false
|
||||
if !healthcheck.Alive {
|
||||
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debugf("Filtering marathon task %s with bad healthcheck", task.AppID)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationFilter(app marathon.Application, filteredTasks []marathon.Task) bool {
|
||||
return fun.Exists(func(task marathon.Task) bool {
|
||||
return task.AppID == app.ID
|
||||
}, filteredTasks)
|
||||
}
|
||||
|
||||
func getApplication(task marathon.Task, apps []marathon.Application) (marathon.Application, error) {
|
||||
for _, application := range apps {
|
||||
if application.ID == task.AppID {
|
||||
return application, nil
|
||||
}
|
||||
}
|
||||
return marathon.Application{}, errors.New("Application not found: " + task.AppID)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getLabel(application marathon.Application, label string) (string, error) {
|
||||
for key, value := range application.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func (provider *Marathon) getPort(task marathon.Task, applications []marathon.Application) string {
|
||||
application, err := getApplication(task, applications)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return ""
|
||||
}
|
||||
|
||||
if portIndexLabel, err := provider.getLabel(application, "traefik.portIndex"); err == nil {
|
||||
if index, err := strconv.Atoi(portIndexLabel); err == nil {
|
||||
return strconv.Itoa(task.Ports[index])
|
||||
}
|
||||
}
|
||||
if portValueLabel, err := provider.getLabel(application, "traefik.port"); err == nil {
|
||||
return portValueLabel
|
||||
}
|
||||
|
||||
for _, port := range task.Ports {
|
||||
return strconv.Itoa(port)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Marathon) getWeight(task marathon.Task, applications []marathon.Application) string {
|
||||
application, errApp := getApplication(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return "0"
|
||||
}
|
||||
if label, err := provider.getLabel(application, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getDomain(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Marathon) getProtocol(task marathon.Task, applications []marathon.Application) string {
|
||||
application, errApp := getApplication(task, applications)
|
||||
if errApp != nil {
|
||||
log.Errorf("Unable to get marathon application from task %s", task.AppID)
|
||||
return "http"
|
||||
}
|
||||
if label, err := provider.getLabel(application, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (provider *Marathon) getPassHostHeader(application marathon.Application) string {
|
||||
if passHostHeader, err := provider.getLabel(application, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
// getFrontendValue returns the frontend value for the specified application, using
|
||||
// it's label. It returns a default one if the label is not present.
|
||||
func (provider *Marathon) getFrontendValue(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
|
||||
return label
|
||||
}
|
||||
return getEscapedName(application.ID) + "." + provider.Domain
|
||||
}
|
||||
|
||||
// getFrontendRule returns the frontend rule for the specified application, using
|
||||
// it's label. It returns a default one (Host) if the label is not present.
|
||||
func (provider *Marathon) getFrontendRule(application marathon.Application) string {
|
||||
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host"
|
||||
}
|
||||
802
provider/marathon_test.go
Normal file
802
provider/marathon_test.go
Normal file
@@ -0,0 +1,802 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/emilevauge/traefik/types"
|
||||
"github.com/gambol99/go-marathon"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
applicationsError bool
|
||||
applications *marathon.Applications
|
||||
tasksError bool
|
||||
tasks *marathon.Tasks
|
||||
}
|
||||
|
||||
func (c *fakeClient) Applications(url.Values) (*marathon.Applications, error) {
|
||||
if c.applicationsError {
|
||||
return nil, errors.New("error")
|
||||
}
|
||||
return c.applications, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) AllTasks(v url.Values) (*marathon.Tasks, error) {
|
||||
if c.tasksError {
|
||||
return nil, errors.New("error")
|
||||
}
|
||||
return c.tasks, nil
|
||||
}
|
||||
|
||||
func TestMarathonLoadConfig(t *testing.T) {
|
||||
cases := []struct {
|
||||
applicationsError bool
|
||||
applications *marathon.Applications
|
||||
tasksError bool
|
||||
tasks *marathon.Tasks
|
||||
expectedNil bool
|
||||
expectedFrontends map[string]*types.Frontend
|
||||
expectedBackends map[string]*types.Backend
|
||||
}{
|
||||
{
|
||||
applications: &marathon.Applications{},
|
||||
tasks: &marathon.Tasks{},
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
applicationsError: true,
|
||||
applications: &marathon.Applications{},
|
||||
tasks: &marathon.Tasks{},
|
||||
expectedNil: true,
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
applications: &marathon.Applications{},
|
||||
tasksError: true,
|
||||
tasks: &marathon.Tasks{},
|
||||
expectedNil: true,
|
||||
expectedFrontends: map[string]*types.Frontend{},
|
||||
expectedBackends: map[string]*types.Backend{},
|
||||
},
|
||||
{
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "/test",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
tasks: &marathon.Tasks{
|
||||
Tasks: []marathon.Task{
|
||||
{
|
||||
ID: "test",
|
||||
AppID: "/test",
|
||||
Host: "127.0.0.1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
`frontend-test`: {
|
||||
Backend: "backend-test",
|
||||
Routes: map[string]types.Route{
|
||||
`route-host-test`: {
|
||||
Rule: "Host",
|
||||
Value: "test.docker.localhost",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-test": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
CircuitBreaker: nil,
|
||||
LoadBalancer: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
provider := &Marathon{
|
||||
Domain: "docker.localhost",
|
||||
marathonClient: &fakeClient{
|
||||
applicationsError: c.applicationsError,
|
||||
applications: c.applications,
|
||||
tasksError: c.tasksError,
|
||||
tasks: c.tasks,
|
||||
},
|
||||
}
|
||||
actualConfig := provider.loadMarathonConfig()
|
||||
if c.expectedNil {
|
||||
if actualConfig != nil {
|
||||
t.Fatalf("Should have been nil, got %v", actualConfig)
|
||||
}
|
||||
} else {
|
||||
// Compare backends
|
||||
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
|
||||
}
|
||||
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
|
||||
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonTaskFilter(t *testing.T) {
|
||||
cases := []struct {
|
||||
task marathon.Task
|
||||
applications *marathon.Applications
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
task: marathon.Task{},
|
||||
applications: &marathon.Applications{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "test",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "test",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
Labels: map[string]string{
|
||||
"traefik.enable": "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "specify-port-number",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "specify-port-number",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "specify-unknown-port-number",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "specify-unknown-port-number",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "specify-port-index",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "specify-port-index",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
"traefik.portIndex": "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "specify-out-of-range-port-index",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "specify-out-of-range-port-index",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
"traefik.portIndex": "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "specify-both-port-index-and-number",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "specify-both-port-index-and-number",
|
||||
Ports: []int{80, 443},
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "443",
|
||||
"traefik.portIndex": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthCheckResult: []*marathon.HealthCheckResult{
|
||||
{
|
||||
Alive: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthCheckResult: []*marathon.HealthCheckResult{
|
||||
{
|
||||
Alive: true,
|
||||
},
|
||||
{
|
||||
Alive: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
task: marathon.Task{
|
||||
AppID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthCheckResult: []*marathon.HealthCheckResult{
|
||||
{
|
||||
Alive: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
applications: &marathon.Applications{
|
||||
Apps: []marathon.Application{
|
||||
{
|
||||
ID: "foo",
|
||||
Ports: []int{80},
|
||||
HealthChecks: []*marathon.HealthCheck{
|
||||
marathon.NewDefaultHealthCheck(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actual := taskFilter(c.task, c.applications)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %v, got %v", c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonApplicationFilter(t *testing.T) {
|
||||
cases := []struct {
|
||||
application marathon.Application
|
||||
filteredTasks []marathon.Task
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
filteredTasks: []marathon.Task{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "test",
|
||||
},
|
||||
filteredTasks: []marathon.Task{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "foo",
|
||||
},
|
||||
filteredTasks: []marathon.Task{
|
||||
{
|
||||
AppID: "bar",
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "foo",
|
||||
},
|
||||
filteredTasks: []marathon.Task{
|
||||
{
|
||||
AppID: "foo",
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actual := applicationFilter(c.application, c.filteredTasks)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %v, got %v", c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetPort(t *testing.T) {
|
||||
provider := &Marathon{}
|
||||
|
||||
cases := []struct {
|
||||
applications []marathon.Application
|
||||
task marathon.Task
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
applications: []marathon.Application{},
|
||||
task: marathon.Task{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test2",
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test1",
|
||||
Ports: []int{80},
|
||||
},
|
||||
expected: "80",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test1",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
expected: "80",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "specify-port-number",
|
||||
Labels: map[string]string{
|
||||
"traefik.port": "443",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "specify-port-number",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
expected: "443",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "specify-port-index",
|
||||
Labels: map[string]string{
|
||||
"traefik.portIndex": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "specify-port-index",
|
||||
Ports: []int{80, 443},
|
||||
},
|
||||
expected: "443",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actual := provider.getPort(c.task, c.applications)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %q, got %q", c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetWeigh(t *testing.T) {
|
||||
provider := &Marathon{}
|
||||
|
||||
applications := []struct {
|
||||
applications []marathon.Application
|
||||
task marathon.Task
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
applications: []marathon.Application{},
|
||||
task: marathon.Task{},
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
Labels: map[string]string{
|
||||
"traefik.weight": "10",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test2",
|
||||
},
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
"traefik.test": "10",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test",
|
||||
},
|
||||
expected: "0",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
"traefik.weight": "10",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test",
|
||||
},
|
||||
expected: "10",
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range applications {
|
||||
actual := provider.getWeight(a.task, a.applications)
|
||||
if actual != a.expected {
|
||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetDomain(t *testing.T) {
|
||||
provider := &Marathon{
|
||||
Domain: "docker.localhost",
|
||||
}
|
||||
|
||||
applications := []struct {
|
||||
application marathon.Application
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
"traefik.domain": "foo.bar",
|
||||
},
|
||||
},
|
||||
expected: "foo.bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range applications {
|
||||
actual := provider.getDomain(a.application)
|
||||
if actual != a.expected {
|
||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetProtocol(t *testing.T) {
|
||||
provider := &Marathon{}
|
||||
|
||||
applications := []struct {
|
||||
applications []marathon.Application
|
||||
task marathon.Task
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
applications: []marathon.Application{},
|
||||
task: marathon.Task{},
|
||||
expected: "http",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test1",
|
||||
Labels: map[string]string{
|
||||
"traefik.protocol": "https",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test2",
|
||||
},
|
||||
expected: "http",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test",
|
||||
},
|
||||
expected: "http",
|
||||
},
|
||||
{
|
||||
applications: []marathon.Application{
|
||||
{
|
||||
ID: "test",
|
||||
Labels: map[string]string{
|
||||
"traefik.protocol": "https",
|
||||
},
|
||||
},
|
||||
},
|
||||
task: marathon.Task{
|
||||
AppID: "test",
|
||||
},
|
||||
expected: "https",
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range applications {
|
||||
actual := provider.getProtocol(a.task, a.applications)
|
||||
if actual != a.expected {
|
||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetPassHostHeader(t *testing.T) {
|
||||
provider := &Marathon{}
|
||||
|
||||
applications := []struct {
|
||||
application marathon.Application
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "false",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.passHostHeader": "true",
|
||||
},
|
||||
},
|
||||
expected: "true",
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range applications {
|
||||
actual := provider.getPassHostHeader(a.application)
|
||||
if actual != a.expected {
|
||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetFrontendValue(t *testing.T) {
|
||||
provider := &Marathon{
|
||||
Domain: "docker.localhost",
|
||||
}
|
||||
|
||||
applications := []struct {
|
||||
application marathon.Application
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: ".docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
ID: "test",
|
||||
},
|
||||
expected: "test.docker.localhost",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.value": "foo.bar",
|
||||
},
|
||||
},
|
||||
expected: "foo.bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range applications {
|
||||
actual := provider.getFrontendValue(a.application)
|
||||
if actual != a.expected {
|
||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarathonGetFrontendRule(t *testing.T) {
|
||||
provider := &Marathon{}
|
||||
|
||||
applications := []struct {
|
||||
application marathon.Application
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
application: marathon.Application{},
|
||||
expected: "Host",
|
||||
},
|
||||
{
|
||||
application: marathon.Application{
|
||||
Labels: map[string]string{
|
||||
"traefik.frontend.rule": "Header",
|
||||
},
|
||||
},
|
||||
expected: "Header",
|
||||
},
|
||||
}
|
||||
|
||||
for _, a := range applications {
|
||||
actual := provider.getFrontendRule(a.application)
|
||||
if actual != a.expected {
|
||||
t.Fatalf("expected %q, got %q", a.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
67
provider/provider.go
Normal file
67
provider/provider.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/emilevauge/traefik/autogen"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Provider defines methods of a provider.
|
||||
type Provider interface {
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
Provide(configurationChan chan<- types.ConfigMessage) error
|
||||
}
|
||||
|
||||
type baseProvider struct {
|
||||
Watch bool
|
||||
Filename string
|
||||
}
|
||||
|
||||
func (p *baseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
)
|
||||
configuration := new(types.Configuration)
|
||||
tmpl := template.New(p.Filename).Funcs(funcMap)
|
||||
if len(p.Filename) > 0 {
|
||||
buf, err = ioutil.ReadFile(p.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
buf, err = autogen.Asset(defaultTemplateFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, err = tmpl.Parse(string(buf))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
err = tmpl.Execute(&buffer, templateObjects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configuration, nil
|
||||
}
|
||||
|
||||
func replace(s1 string, s2 string, s3 string) string {
|
||||
return strings.Replace(s3, s1, s2, -1)
|
||||
}
|
||||
|
||||
func getEscapedName(name string) string {
|
||||
return strings.Replace(name, "/", "", -1)
|
||||
}
|
||||
170
provider/provider_test.go
Normal file
170
provider/provider_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type myProvider struct {
|
||||
baseProvider
|
||||
}
|
||||
|
||||
func (p *myProvider) Foo() string {
|
||||
return "bar"
|
||||
}
|
||||
|
||||
func TestConfigurationErrors(t *testing.T) {
|
||||
templateErrorFile, err := ioutil.TempFile("", "provider-configuration-error")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(templateErrorFile.Name())
|
||||
data := []byte("Not a valid template {{ Bar }}")
|
||||
err = ioutil.WriteFile(templateErrorFile.Name(), data, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
templateInvalidTOMLFile, err := ioutil.TempFile("", "provider-configuration-error")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(templateInvalidTOMLFile.Name())
|
||||
data = []byte(`Hello {{ .Name }}
|
||||
{{ Foo }}`)
|
||||
err = ioutil.WriteFile(templateInvalidTOMLFile.Name(), data, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
invalids := []struct {
|
||||
provider *myProvider
|
||||
defaultTemplate string
|
||||
expectedError string
|
||||
funcMap template.FuncMap
|
||||
templateObjects interface{}
|
||||
}{
|
||||
{
|
||||
provider: &myProvider{
|
||||
baseProvider{
|
||||
Filename: "/non/existent/template.tmpl",
|
||||
},
|
||||
},
|
||||
expectedError: "open /non/existent/template.tmpl: no such file or directory",
|
||||
},
|
||||
{
|
||||
provider: &myProvider{},
|
||||
defaultTemplate: "non/existent/template.tmpl",
|
||||
expectedError: "Asset non/existent/template.tmpl not found",
|
||||
},
|
||||
{
|
||||
provider: &myProvider{
|
||||
baseProvider{
|
||||
Filename: templateErrorFile.Name(),
|
||||
},
|
||||
},
|
||||
expectedError: `function "Bar" not defined`,
|
||||
},
|
||||
{
|
||||
provider: &myProvider{
|
||||
baseProvider{
|
||||
Filename: templateInvalidTOMLFile.Name(),
|
||||
},
|
||||
},
|
||||
expectedError: "Near line 1, key 'Hello': Near line 1: Expected key separator '=', but got '<' instead",
|
||||
funcMap: template.FuncMap{
|
||||
"Foo": func() string {
|
||||
return "bar"
|
||||
},
|
||||
},
|
||||
templateObjects: struct{ Name string }{Name: "bar"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, invalid := range invalids {
|
||||
configuration, err := invalid.provider.getConfiguration(invalid.defaultTemplate, invalid.funcMap, nil)
|
||||
if err == nil || !strings.Contains(err.Error(), invalid.expectedError) {
|
||||
t.Fatalf("should have generate an error with %q, got %v", invalid.expectedError, err)
|
||||
}
|
||||
if configuration != nil {
|
||||
t.Fatalf("shouldn't have return a configuration object : %v", configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfiguration(t *testing.T) {
|
||||
templateFile, err := ioutil.TempFile("", "provider-configuration")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(templateFile.Name())
|
||||
data := []byte(`[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.circuitbreaker]
|
||||
expression = "NetworkErrorRatio() > 0.5"
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
passHostHeader = true
|
||||
[frontends.frontend11.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"`)
|
||||
err = ioutil.WriteFile(templateFile.Name(), data, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provider := &myProvider{
|
||||
baseProvider{
|
||||
Filename: templateFile.Name(),
|
||||
},
|
||||
}
|
||||
configuration, err := provider.getConfiguration(templateFile.Name(), nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have error out, got %v", err)
|
||||
}
|
||||
if configuration == nil {
|
||||
t.Fatalf("Configuration should not be nil, but was")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
cases := []struct {
|
||||
str string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
str: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
str: "foo",
|
||||
expected: "bar",
|
||||
},
|
||||
{
|
||||
str: "foo foo",
|
||||
expected: "bar bar",
|
||||
},
|
||||
{
|
||||
str: "somethingfoo",
|
||||
expected: "somethingbar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
actual := replace("foo", "bar", c.str)
|
||||
if actual != c.expected {
|
||||
t.Fatalf("expected %q, got %q, for %q", c.expected, actual, c.str)
|
||||
}
|
||||
}
|
||||
}
|
||||
20
provider/zk.go
Normal file
20
provider/zk.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/zookeeper"
|
||||
"github.com/emilevauge/traefik/types"
|
||||
)
|
||||
|
||||
// Zookepper holds configurations of the Zookepper provider.
|
||||
type Zookepper struct {
|
||||
Kv
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage) error {
|
||||
provider.StoreType = store.ZK
|
||||
zookeeper.Register()
|
||||
return provider.provide(configurationChan)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[backends]{{range .Containers}}
|
||||
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
|
||||
url = "http://{{.NetworkSettings.IPAddress}}:{{getPort .}}"
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range $host, $containers := .Hosts}}
|
||||
[frontends.frontend-{{$host}}]
|
||||
{{$container := index $containers 0}}
|
||||
backend = "backend-{{getBackend $container}}"
|
||||
[frontends.frontend-{{$host}}.routes.route-host-{{$host}}]
|
||||
rule = "Host"
|
||||
value = "{{$host}}.{{getDomain $container}}"
|
||||
{{end}}
|
||||
@@ -1,27 +0,0 @@
|
||||
{{$apps := .Applications}}
|
||||
[backends]{{range .Tasks}}
|
||||
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
|
||||
url = "http://{{.Host}}:{{getPort .}}"
|
||||
{{$appID := .AppID}}
|
||||
{{range $apps}}
|
||||
{{if eq $appID .ID}}
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{ range $app := .Applications}}
|
||||
{{range $prefix := getPrefixes .}}
|
||||
[frontends.frontend{{$app.ID | replace "/" "-"}}{{$prefix | replace "/" "-"}}]
|
||||
backend = "backend{{$app.ID | replace "/" "-"}}"
|
||||
[frontends.frontend-{{getHost $app | replace "/" "-"}}{{$prefix | replace "/" "-"}}.routes.route-prefix{{$prefix | replace "/" "-"}}]
|
||||
rule = "PathPrefix"
|
||||
value = "{{.}}"
|
||||
{{else}}
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{.ID | replace "/" "-"}}"
|
||||
[frontends.frontend-{{getHost $app | replace "/" "-"}}.routes.route-host-{{getHost $app | replace "/" "-"}}]
|
||||
rule = "Host"
|
||||
value = "{{getHost $app | replace "/" "-"}}.{{getDomain .}}"
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -1,19 +0,0 @@
|
||||
{{$apps := .Applications}}
|
||||
[backends]{{range .Tasks}}
|
||||
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
|
||||
url = "http://{{.Host}}:{{getPort .}}"
|
||||
{{$appID := .AppID}}
|
||||
{{range $apps}}
|
||||
{{if eq $appID .ID}}
|
||||
weight = {{getWeight .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends]{{range .Applications}}
|
||||
[frontends.frontend{{.ID | replace "/" "-"}}]
|
||||
backend = "backend{{.ID | replace "/" "-"}}"
|
||||
[frontends.frontend-{{getHost . | replace "/" "-"}}.routes.route-host-{{getHost . | replace "/" "-"}}]
|
||||
rule = "Host"
|
||||
value = "{{getHost . | replace "/" "-"}}.{{getDomain .}}"
|
||||
{{end}}
|
||||
@@ -1,14 +1,20 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ! test -e gen.go; then
|
||||
if ! test -e autogen/gen.go; then
|
||||
echo >&2 'error: generate must be run before binary'
|
||||
false
|
||||
fi
|
||||
|
||||
rm -f dist/traefik
|
||||
rm -rf Godeps/_workspace/pkg
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION=$(git rev-parse HEAD)
|
||||
fi
|
||||
|
||||
if [ -z "$DATE" ]; then
|
||||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||
fi
|
||||
|
||||
# Build binaries
|
||||
CGO_ENABLED=0 godep go build -a -installsuffix nocgo -o dist/traefik .
|
||||
|
||||
CGO_ENABLED=0 go build -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" -a -installsuffix nocgo -o dist/traefik .
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ! test -e gen.go; then
|
||||
echo >&2 'error: generate must be run before binary'
|
||||
if ! test -e autogen/gen.go; then
|
||||
echo >&2 'error: generate must be run before crossbinary'
|
||||
false
|
||||
fi
|
||||
|
||||
@@ -20,11 +20,17 @@ else
|
||||
OS_ARCH_ARG=($2)
|
||||
fi
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION=$(git rev-parse HEAD)
|
||||
fi
|
||||
|
||||
if [ -z "$DATE" ]; then
|
||||
DATE=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
|
||||
fi
|
||||
|
||||
# Get rid of existing binaries
|
||||
rm -f dist/traefik_*
|
||||
rm -rf Godeps/_workspace/pkg
|
||||
|
||||
# Build binaries
|
||||
GOPATH=`godep path`:$GOPATH gox "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" \
|
||||
gox -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" "${OS_PLATFORM_ARG[@]}" "${OS_ARCH_ARG[@]}" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
|
||||
|
||||
12
script/dockerversion
Normal file
12
script/dockerversion
Normal file
@@ -0,0 +1,12 @@
|
||||
// AUTOGENERATED FILE; see /go/src/github.com/docker/docker/hack/make/.go-autogen
|
||||
package dockerversion
|
||||
|
||||
var (
|
||||
GITCOMMIT string = "traefik-import"
|
||||
VERSION string = "traefik-import"
|
||||
BUILDTIME string = "traefik-import"
|
||||
|
||||
IAMSTATIC string = "traefik-import"
|
||||
INITSHA1 string = "traefik-import"
|
||||
INITPATH string = "traefik-import"
|
||||
)
|
||||
@@ -5,6 +5,7 @@ set -e
|
||||
DEFAULT_BUNDLES=(
|
||||
validate-gofmt
|
||||
validate-govet
|
||||
generate
|
||||
binary
|
||||
|
||||
test-unit
|
||||
|
||||
@@ -6,5 +6,5 @@ export DEST=.
|
||||
TESTFLAGS="$TESTFLAGS -test.timeout=30m -check.v"
|
||||
|
||||
cd integration
|
||||
GOPATH=`godep path`:$GOPATH go test $TESTFLAGS
|
||||
go test $TESTFLAGS
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if ! test -e gen.go; then
|
||||
echo >&2 'error: generate must be run before binary'
|
||||
if ! test -e autogen/gen.go; then
|
||||
echo >&2 'error: generate must be run before test-unit'
|
||||
false
|
||||
fi
|
||||
|
||||
@@ -17,7 +17,7 @@ find_dirs() {
|
||||
find . -not \( \
|
||||
\( \
|
||||
-path './integration/*' \
|
||||
-o -path './Godeps/*' \
|
||||
-o -path './vendor/*' \
|
||||
-o -path './.git/*' \
|
||||
\) \
|
||||
-prune \
|
||||
@@ -34,7 +34,7 @@ TESTS_FAILED=()
|
||||
|
||||
for dir in $TESTDIRS; do
|
||||
echo '+ go test' $TESTFLAGS "${dir}"
|
||||
godep go test ${TESTFLAGS} ${dir}
|
||||
go test ${TESTFLAGS} ${dir}
|
||||
if [ $? != 0 ]; then
|
||||
TESTS_FAILED+=("$dir")
|
||||
echo
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^Godeps' || true) )
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor' || true) )
|
||||
unset IFS
|
||||
|
||||
badFiles=()
|
||||
|
||||
31
script/validate-golint
Executable file
31
script/validate-golint
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/\|autogen' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
for f in "${files[@]}"; do
|
||||
# we use "git show" here to validate that what's committed passes go vet
|
||||
failedLint=$(golint "$f")
|
||||
if [ "$failedLint" ]; then
|
||||
errors+=( "$failedLint" )
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#errors[@]} -eq 0 ]; then
|
||||
echo 'Congratulations! All Go source files have been linted.'
|
||||
else
|
||||
{
|
||||
echo "Errors from golint:"
|
||||
for err in "${errors[@]}"; do
|
||||
echo "$err"
|
||||
done
|
||||
echo
|
||||
echo 'Please fix the above errors. You can test via "golint" and commit the result.'
|
||||
echo
|
||||
} >&2
|
||||
false
|
||||
fi
|
||||
@@ -3,7 +3,7 @@
|
||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^Godeps/' || true) )
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
indent_size = 2
|
||||
2
static/.gitignore
vendored
2
static/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
# bower_components
|
||||
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
bower_components
|
||||
120
static/.jshintrc
120
static/.jshintrc
@@ -1,120 +0,0 @@
|
||||
{
|
||||
// JSHint Default Configuration File (as on JSHint website)
|
||||
// See http://jshint.com/docs/ for more details
|
||||
|
||||
"maxerr" : 500, // {int} Maximum error before stopping (default:50)
|
||||
|
||||
// Enforcing
|
||||
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||
"camelcase" : false, // true: Identifiers must be in camelCase
|
||||
"curly" : true, // true: Require {} for every new block or scope
|
||||
"eqeqeq" : true, // true: Require triple equals (===) for comparison
|
||||
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
|
||||
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
|
||||
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||
"indent" : 4, // {int} Number of spaces to use for indentation
|
||||
"latedef" : true, // true: Require variables/functions to be defined before being used
|
||||
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
|
||||
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
|
||||
"noempty" : true, // true: Prohibit use of empty blocks
|
||||
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
|
||||
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
|
||||
"plusplus" : false, // true: Prohibit use of `++` & `--`
|
||||
"quotmark" : false, // Quotation mark consistency:
|
||||
// false : do nothing (default)
|
||||
// true : ensure whatever is used is consistent
|
||||
// "single" : require single quotes
|
||||
// "double" : require double quotes
|
||||
"regexp" : true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
|
||||
"unused" : true, // true: Require all defined variables be used
|
||||
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
|
||||
"trailing" : true, // Prohibit trailing whitespaces.
|
||||
"maxparams" : false, // {int} Max number of formal params allowed per function
|
||||
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
|
||||
"maxstatements" : false, // {int} Max number statements per function
|
||||
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
|
||||
"maxlen" : false, // {int} Max number of characters per line
|
||||
|
||||
// Relaxing
|
||||
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
|
||||
"boss" : false, // true: Tolerate assignments where comparisons would be expected
|
||||
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
|
||||
"eqnull" : false, // true: Tolerate use of `== null`
|
||||
//"es5" : true, // true: Allow ES5 syntax (ex: getters and setters) [default: true] if override to true -> generate error
|
||||
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
|
||||
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
|
||||
// (ex: `for each`, multiple try/catch, function expression…)
|
||||
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
|
||||
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
|
||||
"funcscope" : false, // true: Tolerate defining variables inside control statements
|
||||
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
|
||||
"iterator" : false, // true: Tolerate using the `__iterator__` property
|
||||
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
|
||||
"laxbreak" : true, // true: Tolerate possibly unsafe line breakings
|
||||
"laxcomma" : true, // true: Tolerate comma-first style coding
|
||||
"loopfunc" : false, // true: Tolerate functions being defined in loops
|
||||
"multistr" : false, // true: Tolerate multi-line strings
|
||||
"noyield" : false, // true: Tolerate generator functions with no yield statement in them.
|
||||
"notypeof" : false, // true: Tolerate invalid typeof operator values
|
||||
"proto" : false, // true: Tolerate using the `__proto__` property
|
||||
"scripturl" : false, // true: Tolerate script-targeted URLs
|
||||
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
|
||||
"smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignment only.
|
||||
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
|
||||
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
|
||||
"validthis" : false, // true: Tolerate using this in a non-constructor function
|
||||
|
||||
// JSLint Legacy
|
||||
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
|
||||
"onevar" : false, // Allow only one `var` statement per function.
|
||||
"passfail" : false, // Stop on first error.
|
||||
"white" : false, // Check against strict whitespace and indentation rules.
|
||||
|
||||
// Environments
|
||||
"browser" : true, // Web Browser (window, document, etc)
|
||||
//"browserify" : false, // Browserify (node.js code in the browser)
|
||||
"couch" : false, // CouchDB
|
||||
"devel" : false, // Development/debugging (alert, confirm, etc)
|
||||
"dojo" : false, // Dojo Toolkit
|
||||
"jasmine" : true, // Jasmine
|
||||
"jquery" : true, // jQuery
|
||||
"mocha" : true, // Mocha
|
||||
"mootools" : false, // MooTools
|
||||
"node" : true, // Node.js
|
||||
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
|
||||
"prototypejs" : false, // Prototype and Scriptaculous
|
||||
"qunit" : false, // QUnit
|
||||
"rhino" : false, // Rhino
|
||||
"shelljs" : false, // ShellJS
|
||||
"worker" : false, // Web Workers
|
||||
"wsh" : false, // Windows Scripting Host
|
||||
"yui" : false, // Yahoo User Interface
|
||||
|
||||
// Custom Globals
|
||||
// additional predefined global variables
|
||||
"globals" : {
|
||||
"angular": false,
|
||||
"require": false,
|
||||
"process": false,
|
||||
"module": false,
|
||||
"__dirname": false,
|
||||
"browser": false,
|
||||
"repeater": false,
|
||||
"element": false,
|
||||
"inject": false,
|
||||
"afterEach": false,
|
||||
"beforeEach": false,
|
||||
"confirm": false,
|
||||
"describe": false,
|
||||
"expect": false,
|
||||
"it": false,
|
||||
"jasmine": false,
|
||||
"by": false,
|
||||
"runs": false,
|
||||
"spyOn": false,
|
||||
"spyOnEvent": false,
|
||||
"waitsFor": false,
|
||||
"xdescribe": false
|
||||
}
|
||||
}
|
||||
1
static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md
Normal file
1
static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md
Normal file
@@ -0,0 +1 @@
|
||||
For more informations show `webui/readme.md`
|
||||
@@ -1,10 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('traefik.core.health', ['ngResource'])
|
||||
.factory('Health', ['$resource', function ($resource) {
|
||||
return $resource('/health');
|
||||
}]);
|
||||
|
||||
})();
|
||||
@@ -1,10 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('traefik.section.providers.backend-monitor')
|
||||
.controller('BackendMonitorController', function () {
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -1,10 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('traefik.section.providers.frontend-monitor')
|
||||
.controller('FrontendMonitorController', function () {
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -1,6 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular.module('traefik', ['traefik.section']);
|
||||
|
||||
})();
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "traefik",
|
||||
"version": "1.0.0",
|
||||
"homepage": "http://traefik.io",
|
||||
"authors": [
|
||||
"Fernandez Ludovic <ludovic.fernandez@zenika.com>"
|
||||
],
|
||||
"description": "Front end for Træfɪk",
|
||||
"main": "index.html",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"angular": "angularjs#~1.4.7",
|
||||
"bootstrap": "~3.3.5",
|
||||
"angular-resource": "~1.4.7",
|
||||
"angular-ui-router": "~0.2.15",
|
||||
"angular-bootstrap": "~0.13.4",
|
||||
"angular-nvd3": "~1.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/* Include this file in your html if you are using the CSP mode. */
|
||||
|
||||
.ng-animate.item:not(.left):not(.right) {
|
||||
-webkit-transition: 0s ease-in-out left;
|
||||
transition: 0s ease-in-out left
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.4.7
|
||||
(c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(I,f,C){'use strict';function D(t,e){e=e||{};f.forEach(e,function(f,k){delete e[k]});for(var k in t)!t.hasOwnProperty(k)||"$"===k.charAt(0)&&"$"===k.charAt(1)||(e[k]=t[k]);return e}var y=f.$$minErr("$resource"),B=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;f.module("ngResource",["ng"]).provider("$resource",function(){var t=/^https?:\/\/[^\/]*/,e=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}};
|
||||
this.$get=["$http","$q",function(k,F){function w(f,g){this.template=f;this.defaults=r({},e.defaults,g);this.urlParams={}}function z(l,g,s,h){function c(b,q){var c={};q=r({},g,q);u(q,function(a,q){x(a)&&(a=a());var m;if(a&&a.charAt&&"@"==a.charAt(0)){m=b;var d=a.substr(1);if(null==d||""===d||"hasOwnProperty"===d||!B.test("."+d))throw y("badmember",d);for(var d=d.split("."),n=0,g=d.length;n<g&&f.isDefined(m);n++){var e=d[n];m=null!==m?m[e]:C}}else m=a;c[q]=m});return c}function G(b){return b.resource}
|
||||
function d(b){D(b||{},this)}var t=new w(l,h);s=r({},e.defaults.actions,s);d.prototype.toJSON=function(){var b=r({},this);delete b.$promise;delete b.$resolved;return b};u(s,function(b,q){var g=/^(POST|PUT|PATCH)$/i.test(b.method);d[q]=function(a,A,m,e){var n={},h,l,s;switch(arguments.length){case 4:s=e,l=m;case 3:case 2:if(x(A)){if(x(a)){l=a;s=A;break}l=A;s=m}else{n=a;h=A;l=m;break}case 1:x(a)?l=a:g?h=a:n=a;break;case 0:break;default:throw y("badargs",arguments.length);}var w=this instanceof d,p=w?
|
||||
h:b.isArray?[]:new d(h),v={},z=b.interceptor&&b.interceptor.response||G,B=b.interceptor&&b.interceptor.responseError||C;u(b,function(b,a){"params"!=a&&"isArray"!=a&&"interceptor"!=a&&(v[a]=H(b))});g&&(v.data=h);t.setUrlParams(v,r({},c(h,b.params||{}),n),b.url);n=k(v).then(function(a){var c=a.data,m=p.$promise;if(c){if(f.isArray(c)!==!!b.isArray)throw y("badcfg",q,b.isArray?"array":"object",f.isArray(c)?"array":"object",v.method,v.url);b.isArray?(p.length=0,u(c,function(a){"object"===typeof a?p.push(new d(a)):
|
||||
p.push(a)})):(D(c,p),p.$promise=m)}p.$resolved=!0;a.resource=p;return a},function(a){p.$resolved=!0;(s||E)(a);return F.reject(a)});n=n.then(function(a){var b=z(a);(l||E)(b,a.headers);return b},B);return w?n:(p.$promise=n,p.$resolved=!1,p)};d.prototype["$"+q]=function(a,b,c){x(a)&&(c=b,b=a,a={});a=d[q].call(this,a,this,b,c);return a.$promise||a}});d.bind=function(b){return z(l,r({},g,b),s)};return d}var E=f.noop,u=f.forEach,r=f.extend,H=f.copy,x=f.isFunction;w.prototype={setUrlParams:function(l,g,
|
||||
e){var h=this,c=e||h.template,k,d,r="",b=h.urlParams={};u(c.split(/\W/),function(d){if("hasOwnProperty"===d)throw y("badname");!/^\d+$/.test(d)&&d&&(new RegExp("(^|[^\\\\]):"+d+"(\\W|$)")).test(c)&&(b[d]=!0)});c=c.replace(/\\:/g,":");c=c.replace(t,function(b){r=b;return""});g=g||{};u(h.urlParams,function(b,e){k=g.hasOwnProperty(e)?g[e]:h.defaults[e];f.isDefined(k)&&null!==k?(d=encodeURIComponent(k).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,
|
||||
"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),c=c.replace(new RegExp(":"+e+"(\\W|$)","g"),function(a,b){return d+b})):c=c.replace(new RegExp("(/?):"+e+"(\\W|$)","g"),function(a,b,c){return"/"==c.charAt(0)?c:b+c})});h.defaults.stripTrailingSlashes&&(c=c.replace(/\/+$/,"")||"/");c=c.replace(/\/\.(?=\w+($|\?))/,".");l.url=r+c.replace(/\/\\\./,"/.");u(g,function(b,c){h.urlParams[c]||(l.params=l.params||{},l.params[c]=b)})}};return z}]})})(window,window.angular);
|
||||
//# sourceMappingURL=angular-resource.min.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
21
static/bower_components/angular/angular-csp.css
vendored
21
static/bower_components/angular/angular-csp.css
vendored
@@ -1,21 +0,0 @@
|
||||
/* Include this file in your html if you are using the CSP mode. */
|
||||
|
||||
@charset "UTF-8";
|
||||
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
|
||||
.ng-cloak, .x-ng-cloak,
|
||||
.ng-hide:not(.ng-hide-animate) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
ng\:form {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ng-animate-shim {
|
||||
visibility:hidden;
|
||||
}
|
||||
|
||||
.ng-anchor {
|
||||
position:absolute;
|
||||
}
|
||||
294
static/bower_components/angular/angular.min.js
vendored
294
static/bower_components/angular/angular.min.js
vendored
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
AngularJS v1.4.7
|
||||
(c) 2010-2015 Google, Inc. http://angularjs.org
|
||||
License: MIT
|
||||
*/
|
||||
(function(Q,X,w){'use strict';function I(b){return function(){var a=arguments[0],c;c="["+(b?b+":":"")+a+"] http://errors.angularjs.org/1.4.7/"+(b?b+"/":"")+a;for(a=1;a<arguments.length;a++){c=c+(1==a?"?":"&")+"p"+(a-1)+"=";var d=encodeURIComponent,e;e=arguments[a];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;c+=d(e)}return Error(c)}}function Da(b){if(null==b||Za(b))return!1;var a="length"in Object(b)&&b.length;
|
||||
return b.nodeType===pa&&a?!0:G(b)||J(b)||0===a||"number"===typeof a&&0<a&&a-1 in b}function m(b,a,c){var d,e;if(b)if(x(b))for(d in b)"prototype"==d||"length"==d||"name"==d||b.hasOwnProperty&&!b.hasOwnProperty(d)||a.call(c,b[d],d,b);else if(J(b)||Da(b)){var f="object"!==typeof b;d=0;for(e=b.length;d<e;d++)(f||d in b)&&a.call(c,b[d],d,b)}else if(b.forEach&&b.forEach!==m)b.forEach(a,c,b);else if(mc(b))for(d in b)a.call(c,b[d],d,b);else if("function"===typeof b.hasOwnProperty)for(d in b)b.hasOwnProperty(d)&&
|
||||
a.call(c,b[d],d,b);else for(d in b)ta.call(b,d)&&a.call(c,b[d],d,b);return b}function nc(b,a,c){for(var d=Object.keys(b).sort(),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function oc(b){return function(a,c){b(c,a)}}function Ud(){return++nb}function pc(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function Mb(b,a,c){for(var d=b.$$hashKey,e=0,f=a.length;e<f;++e){var h=a[e];if(C(h)||x(h))for(var g=Object.keys(h),l=0,k=g.length;l<k;l++){var n=g[l],p=h[n];c&&C(p)?ea(p)?b[n]=new Date(p.valueOf()):Oa(p)?
|
||||
b[n]=new RegExp(p):(C(b[n])||(b[n]=J(p)?[]:{}),Mb(b[n],[p],!0)):b[n]=p}}pc(b,d);return b}function P(b){return Mb(b,ua.call(arguments,1),!1)}function Vd(b){return Mb(b,ua.call(arguments,1),!0)}function Y(b){return parseInt(b,10)}function Nb(b,a){return P(Object.create(b),a)}function y(){}function $a(b){return b}function qa(b){return function(){return b}}function qc(b){return x(b.toString)&&b.toString!==Object.prototype.toString}function v(b){return"undefined"===typeof b}function A(b){return"undefined"!==
|
||||
typeof b}function C(b){return null!==b&&"object"===typeof b}function mc(b){return null!==b&&"object"===typeof b&&!rc(b)}function G(b){return"string"===typeof b}function V(b){return"number"===typeof b}function ea(b){return"[object Date]"===va.call(b)}function x(b){return"function"===typeof b}function Oa(b){return"[object RegExp]"===va.call(b)}function Za(b){return b&&b.window===b}function ab(b){return b&&b.$evalAsync&&b.$watch}function bb(b){return"boolean"===typeof b}function sc(b){return!(!b||!(b.nodeName||
|
||||
b.prop&&b.attr&&b.find))}function Wd(b){var a={};b=b.split(",");var c;for(c=0;c<b.length;c++)a[b[c]]=!0;return a}function wa(b){return F(b.nodeName||b[0]&&b[0].nodeName)}function cb(b,a){var c=b.indexOf(a);0<=c&&b.splice(c,1);return c}function ha(b,a,c,d){if(Za(b)||ab(b))throw Ea("cpws");if(tc.test(va.call(a)))throw Ea("cpta");if(a){if(b===a)throw Ea("cpi");c=c||[];d=d||[];C(b)&&(c.push(b),d.push(a));var e;if(J(b))for(e=a.length=0;e<b.length;e++)a.push(ha(b[e],null,c,d));else{var f=a.$$hashKey;J(a)?
|
||||
a.length=0:m(a,function(b,c){delete a[c]});if(mc(b))for(e in b)a[e]=ha(b[e],null,c,d);else if(b&&"function"===typeof b.hasOwnProperty)for(e in b)b.hasOwnProperty(e)&&(a[e]=ha(b[e],null,c,d));else for(e in b)ta.call(b,e)&&(a[e]=ha(b[e],null,c,d));pc(a,f)}}else if(a=b,C(b)){if(c&&-1!==(f=c.indexOf(b)))return d[f];if(J(b))return ha(b,[],c,d);if(tc.test(va.call(b)))a=new b.constructor(b);else if(ea(b))a=new Date(b.getTime());else if(Oa(b))a=new RegExp(b.source,b.toString().match(/[^\/]*$/)[0]),a.lastIndex=
|
||||
b.lastIndex;else if(x(b.cloneNode))a=b.cloneNode(!0);else return e=Object.create(rc(b)),ha(b,e,c,d);d&&(c.push(b),d.push(a))}return a}function ja(b,a){if(J(b)){a=a||[];for(var c=0,d=b.length;c<d;c++)a[c]=b[c]}else if(C(b))for(c in a=a||{},b)if("$"!==c.charAt(0)||"$"!==c.charAt(1))a[c]=b[c];return a||b}function ka(b,a){if(b===a)return!0;if(null===b||null===a)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&"object"==c)if(J(b)){if(!J(a))return!1;if((c=b.length)==a.length){for(d=0;d<
|
||||
c;d++)if(!ka(b[d],a[d]))return!1;return!0}}else{if(ea(b))return ea(a)?ka(b.getTime(),a.getTime()):!1;if(Oa(b))return Oa(a)?b.toString()==a.toString():!1;if(ab(b)||ab(a)||Za(b)||Za(a)||J(a)||ea(a)||Oa(a))return!1;c=fa();for(d in b)if("$"!==d.charAt(0)&&!x(b[d])){if(!ka(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!(d in c)&&"$"!==d.charAt(0)&&A(a[d])&&!x(a[d]))return!1;return!0}return!1}function db(b,a,c){return b.concat(ua.call(a,c))}function uc(b,a){var c=2<arguments.length?ua.call(arguments,2):[];
|
||||
return!x(a)||a instanceof RegExp?a:c.length?function(){return arguments.length?a.apply(b,db(c,arguments,0)):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}}function Xd(b,a){var c=a;"string"===typeof b&&"$"===b.charAt(0)&&"$"===b.charAt(1)?c=w:Za(a)?c="$WINDOW":a&&X===a?c="$DOCUMENT":ab(a)&&(c="$SCOPE");return c}function eb(b,a){if("undefined"===typeof b)return w;V(a)||(a=a?2:null);return JSON.stringify(b,Xd,a)}function vc(b){return G(b)?JSON.parse(b):b}function wc(b,
|
||||
a){var c=Date.parse("Jan 01, 1970 00:00:00 "+b)/6E4;return isNaN(c)?a:c}function Ob(b,a,c){c=c?-1:1;var d=wc(a,b.getTimezoneOffset());a=b;b=c*(d-b.getTimezoneOffset());a=new Date(a.getTime());a.setMinutes(a.getMinutes()+b);return a}function xa(b){b=B(b).clone();try{b.empty()}catch(a){}var c=B("<div>").append(b).html();try{return b[0].nodeType===Pa?F(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+F(b)})}catch(d){return F(c)}}function xc(b){try{return decodeURIComponent(b)}catch(a){}}
|
||||
function yc(b){var a={};m((b||"").split("&"),function(b){var d,e,f;b&&(e=b=b.replace(/\+/g,"%20"),d=b.indexOf("="),-1!==d&&(e=b.substring(0,d),f=b.substring(d+1)),e=xc(e),A(e)&&(f=A(f)?xc(f):!0,ta.call(a,e)?J(a[e])?a[e].push(f):a[e]=[a[e],f]:a[e]=f))});return a}function Pb(b){var a=[];m(b,function(b,d){J(b)?m(b,function(b){a.push(la(d,!0)+(!0===b?"":"="+la(b,!0)))}):a.push(la(d,!0)+(!0===b?"":"="+la(b,!0)))});return a.length?a.join("&"):""}function ob(b){return la(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,
|
||||
"=").replace(/%2B/gi,"+")}function la(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Yd(b,a){var c,d,e=Qa.length;for(d=0;d<e;++d)if(c=Qa[d]+a,G(c=b.getAttribute(c)))return c;return null}function Zd(b,a){var c,d,e={};m(Qa,function(a){a+="app";!c&&b.hasAttribute&&b.hasAttribute(a)&&(c=b,d=b.getAttribute(a))});m(Qa,function(a){a+="app";var e;!c&&(e=b.querySelector("["+a.replace(":",
|
||||
"\\:")+"]"))&&(c=e,d=e.getAttribute(a))});c&&(e.strictDi=null!==Yd(c,"strict-di"),a(c,d?[d]:[],e))}function zc(b,a,c){C(c)||(c={});c=P({strictDi:!1},c);var d=function(){b=B(b);if(b.injector()){var d=b[0]===X?"document":xa(b);throw Ea("btstrpd",d.replace(/</,"<").replace(/>/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=fb(a,c.strictDi);d.invoke(["$rootScope",
|
||||
"$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return d},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;Q&&e.test(Q.name)&&(c.debugInfoEnabled=!0,Q.name=Q.name.replace(e,""));if(Q&&!f.test(Q.name))return d();Q.name=Q.name.replace(f,"");da.resumeBootstrap=function(b){m(b,function(b){a.push(b)});return d()};x(da.resumeDeferredBootstrap)&&da.resumeDeferredBootstrap()}function $d(){Q.name="NG_ENABLE_DEBUG_INFO!"+Q.name;Q.location.reload()}
|
||||
function ae(b){b=da.element(b).injector();if(!b)throw Ea("test");return b.get("$$testability")}function Ac(b,a){a=a||"_";return b.replace(be,function(b,d){return(d?a:"")+b.toLowerCase()})}function ce(){var b;if(!Bc){var a=pb();(ra=v(a)?Q.jQuery:a?Q[a]:w)&&ra.fn.on?(B=ra,P(ra.fn,{scope:Ra.scope,isolateScope:Ra.isolateScope,controller:Ra.controller,injector:Ra.injector,inheritedData:Ra.inheritedData}),b=ra.cleanData,ra.cleanData=function(a){var d;if(Qb)Qb=!1;else for(var e=0,f;null!=(f=a[e]);e++)(d=
|
||||
ra._data(f,"events"))&&d.$destroy&&ra(f).triggerHandler("$destroy");b(a)}):B=R;da.element=B;Bc=!0}}function qb(b,a,c){if(!b)throw Ea("areq",a||"?",c||"required");return b}function Sa(b,a,c){c&&J(b)&&(b=b[b.length-1]);qb(x(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ta(b,a){if("hasOwnProperty"===b)throw Ea("badname",a);}function Cc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,h=0;h<f;h++)d=a[h],b&&(b=(e=b)[d]);return!c&&
|
||||
x(b)?uc(e,b):b}function rb(b){for(var a=b[0],c=b[b.length-1],d,e=1;a!==c&&(a=a.nextSibling);e++)if(d||b[e]!==a)d||(d=B(ua.call(b,0,e))),d.push(a);return d||b}function fa(){return Object.create(null)}function de(b){function a(a,b,c){return a[b]||(a[b]=c())}var c=I("$injector"),d=I("ng");b=a(b,"angular",Object);b.$$minErr=b.$$minErr||I;return a(b,"module",function(){var b={};return function(f,h,g){if("hasOwnProperty"===f)throw d("badname","module");h&&b.hasOwnProperty(f)&&(b[f]=null);return a(b,f,function(){function a(b,
|
||||
c,e,f){f||(f=d);return function(){f[e||"push"]([b,c,arguments]);return E}}function b(a,c){return function(b,e){e&&x(e)&&(e.$$moduleName=f);d.push([a,c,arguments]);return E}}if(!h)throw c("nomod",f);var d=[],e=[],r=[],t=a("$injector","invoke","push",e),E={_invokeQueue:d,_configBlocks:e,_runBlocks:r,requires:h,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide",
|
||||
"decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider","register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),config:t,run:function(a){r.push(a);return this}};g&&t(g);return E})}})}function ee(b){P(b,{bootstrap:zc,copy:ha,extend:P,merge:Vd,equals:ka,element:B,forEach:m,injector:fb,noop:y,bind:uc,toJson:eb,fromJson:vc,identity:$a,isUndefined:v,isDefined:A,isString:G,isFunction:x,isObject:C,isNumber:V,isElement:sc,isArray:J,
|
||||
version:fe,isDate:ea,lowercase:F,uppercase:sb,callbacks:{counter:0},getTestability:ae,$$minErr:I,$$csp:Fa,reloadWithDebugInfo:$d});Rb=de(Q);Rb("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:ge});a.provider("$compile",Dc).directive({a:he,input:Ec,textarea:Ec,form:ie,script:je,select:ke,style:le,option:me,ngBind:ne,ngBindHtml:oe,ngBindTemplate:pe,ngClass:qe,ngClassEven:re,ngClassOdd:se,ngCloak:te,ngController:ue,ngForm:ve,ngHide:we,ngIf:xe,ngInclude:ye,ngInit:ze,ngNonBindable:Ae,
|
||||
ngPluralize:Be,ngRepeat:Ce,ngShow:De,ngStyle:Ee,ngSwitch:Fe,ngSwitchWhen:Ge,ngSwitchDefault:He,ngOptions:Ie,ngTransclude:Je,ngModel:Ke,ngList:Le,ngChange:Me,pattern:Fc,ngPattern:Fc,required:Gc,ngRequired:Gc,minlength:Hc,ngMinlength:Hc,maxlength:Ic,ngMaxlength:Ic,ngValue:Ne,ngModelOptions:Oe}).directive({ngInclude:Pe}).directive(tb).directive(Jc);a.provider({$anchorScroll:Qe,$animate:Re,$animateCss:Se,$$animateQueue:Te,$$AnimateRunner:Ue,$browser:Ve,$cacheFactory:We,$controller:Xe,$document:Ye,$exceptionHandler:Ze,
|
||||
$filter:Kc,$$forceReflow:$e,$interpolate:af,$interval:bf,$http:cf,$httpParamSerializer:df,$httpParamSerializerJQLike:ef,$httpBackend:ff,$xhrFactory:gf,$location:hf,$log:jf,$parse:kf,$rootScope:lf,$q:mf,$$q:nf,$sce:of,$sceDelegate:pf,$sniffer:qf,$templateCache:rf,$templateRequest:sf,$$testability:tf,$timeout:uf,$window:vf,$$rAF:wf,$$jqLite:xf,$$HashMap:yf,$$cookieReader:zf})}])}function gb(b){return b.replace(Af,function(a,b,d,e){return e?d.toUpperCase():d}).replace(Bf,"Moz$1")}function Lc(b){b=b.nodeType;
|
||||
return b===pa||!b||9===b}function Mc(b,a){var c,d,e=a.createDocumentFragment(),f=[];if(Sb.test(b)){c=c||e.appendChild(a.createElement("div"));d=(Cf.exec(b)||["",""])[1].toLowerCase();d=ma[d]||ma._default;c.innerHTML=d[1]+b.replace(Df,"<$1></$2>")+d[2];for(d=d[0];d--;)c=c.lastChild;f=db(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";m(f,function(a){e.appendChild(a)});return e}function R(b){if(b instanceof R)return b;var a;G(b)&&(b=T(b),
|
||||
a=!0);if(!(this instanceof R)){if(a&&"<"!=b.charAt(0))throw Tb("nosel");return new R(b)}if(a){a=X;var c;b=(c=Ef.exec(b))?[a.createElement(c[1])]:(c=Mc(b,a))?c.childNodes:[]}Nc(this,b)}function Ub(b){return b.cloneNode(!0)}function ub(b,a){a||vb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d<e;d++)vb(c[d])}function Oc(b,a,c,d){if(A(d))throw Tb("offargs");var e=(d=wb(b))&&d.events,f=d&&d.handle;if(f)if(a)m(a.split(" "),function(a){if(A(c)){var d=e[a];cb(d||[],c);if(d&&0<
|
||||
d.length)return}b.removeEventListener(a,f,!1);delete e[a]});else for(a in e)"$destroy"!==a&&b.removeEventListener(a,f,!1),delete e[a]}function vb(b,a){var c=b.ng339,d=c&&hb[c];d&&(a?delete d.data[a]:(d.handle&&(d.events.$destroy&&d.handle({},"$destroy"),Oc(b)),delete hb[c],b.ng339=w))}function wb(b,a){var c=b.ng339,c=c&&hb[c];a&&!c&&(b.ng339=c=++Ff,c=hb[c]={events:{},data:{},handle:w});return c}function Vb(b,a,c){if(Lc(b)){var d=A(c),e=!d&&a&&!C(a),f=!a;b=(b=wb(b,!e))&&b.data;if(d)b[a]=c;else{if(f)return b;
|
||||
if(e)return b&&b[a];P(b,a)}}}function xb(b,a){return b.getAttribute?-1<(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" "):!1}function yb(b,a){a&&b.setAttribute&&m(a.split(" "),function(a){b.setAttribute("class",T((" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+T(a)+" "," ")))})}function zb(b,a){if(a&&b.setAttribute){var c=(" "+(b.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");m(a.split(" "),function(a){a=T(a);-1===c.indexOf(" "+a+" ")&&
|
||||
(c+=a+" ")});b.setAttribute("class",T(c))}}function Nc(b,a){if(a)if(a.nodeType)b[b.length++]=a;else{var c=a.length;if("number"===typeof c&&a.window!==a){if(c)for(var d=0;d<c;d++)b[b.length++]=a[d]}else b[b.length++]=a}}function Pc(b,a){return Ab(b,"$"+(a||"ngController")+"Controller")}function Ab(b,a,c){9==b.nodeType&&(b=b.documentElement);for(a=J(a)?a:[a];b;){for(var d=0,e=a.length;d<e;d++)if(A(c=B.data(b,a[d])))return c;b=b.parentNode||11===b.nodeType&&b.host}}function Qc(b){for(ub(b,!0);b.firstChild;)b.removeChild(b.firstChild)}
|
||||
function Wb(b,a){a||ub(b);var c=b.parentNode;c&&c.removeChild(b)}function Gf(b,a){a=a||Q;if("complete"===a.document.readyState)a.setTimeout(b);else B(a).on("load",b)}function Rc(b,a){var c=Bb[a.toLowerCase()];return c&&Sc[wa(b)]&&c}function Hf(b,a){var c=function(c,e){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=a[e||c.type],h=f?f.length:0;if(h){if(v(c.immediatePropagationStopped)){var g=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=
|
||||
!0;c.stopPropagation&&c.stopPropagation();g&&g.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};1<h&&(f=ja(f));for(var l=0;l<h;l++)c.isImmediatePropagationStopped()||f[l].call(b,c)}};c.elem=b;return c}function xf(){this.$get=function(){return P(R,{hasClass:function(b,a){b.attr&&(b=b[0]);return xb(b,a)},addClass:function(b,a){b.attr&&(b=b[0]);return zb(b,a)},removeClass:function(b,a){b.attr&&(b=b[0]);return yb(b,a)}})}}function Ga(b,a){var c=b&&b.$$hashKey;
|
||||
if(c)return"function"===typeof c&&(c=b.$$hashKey()),c;c=typeof b;return c="function"==c||"object"==c&&null!==b?b.$$hashKey=c+":"+(a||Ud)():c+":"+b}function Ua(b,a){if(a){var c=0;this.nextUid=function(){return++c}}m(b,this.put,this)}function If(b){return(b=b.toString().replace(Tc,"").match(Uc))?"function("+(b[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function fb(b,a){function c(a){return function(b,c){if(C(b))m(b,oc(a));else return a(b,c)}}function d(a,b){Ta(a,"service");if(x(b)||J(b))b=r.instantiate(b);
|
||||
if(!b.$get)throw Ha("pget",a);return p[a+"Provider"]=b}function e(a,b){return function(){var c=E.invoke(b,this);if(v(c))throw Ha("undef",a);return c}}function f(a,b,c){return d(a,{$get:!1!==c?e(a,b):b})}function h(a){qb(v(a)||J(a),"modulesToLoad","not an array");var b=[],c;m(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=r.get(e[0]);f[e[1]].apply(f,e[2])}}if(!n.get(a)){n.put(a,!0);try{G(a)?(c=Rb(a),b=b.concat(h(c.requires)).concat(c._runBlocks),d(c._invokeQueue),d(c._configBlocks)):
|
||||
x(a)?b.push(r.invoke(a)):J(a)?b.push(r.invoke(a)):Sa(a,"module")}catch(e){throw J(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ha("modulerr",a,e.stack||e.message||e);}}});return b}function g(b,c){function d(a,e){if(b.hasOwnProperty(a)){if(b[a]===l)throw Ha("cdep",a+" <- "+k.join(" <- "));return b[a]}try{return k.unshift(a),b[a]=l,b[a]=c(a,e)}catch(f){throw b[a]===l&&delete b[a],f;}finally{k.shift()}}function e(b,c,f,g){"string"===typeof f&&(g=
|
||||
f,f=null);var h=[],k=fb.$$annotate(b,a,g),l,r,p;r=0;for(l=k.length;r<l;r++){p=k[r];if("string"!==typeof p)throw Ha("itkn",p);h.push(f&&f.hasOwnProperty(p)?f[p]:d(p,g))}J(b)&&(b=b[l]);return b.apply(c,h)}return{invoke:e,instantiate:function(a,b,c){var d=Object.create((J(a)?a[a.length-1]:a).prototype||null);a=e(a,d,b,c);return C(a)||x(a)?a:d},get:d,annotate:fb.$$annotate,has:function(a){return p.hasOwnProperty(a+"Provider")||b.hasOwnProperty(a)}}}a=!0===a;var l={},k=[],n=new Ua([],!0),p={$provide:{provider:c(d),
|
||||
factory:c(f),service:c(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:c(function(a,b){return f(a,qa(b),!1)}),constant:c(function(a,b){Ta(a,"constant");p[a]=b;t[a]=b}),decorator:function(a,b){var c=r.get(a+"Provider"),d=c.$get;c.$get=function(){var a=E.invoke(d,c);return E.invoke(b,null,{$delegate:a})}}}},r=p.$injector=g(p,function(a,b){da.isString(b)&&k.push(b);throw Ha("unpr",k.join(" <- "));}),t={},E=t.$injector=g(t,function(a,b){var c=r.get(a+"Provider",b);
|
||||
return E.invoke(c.$get,c,w,a)});m(h(b),function(a){a&&E.invoke(a)});return E}function Qe(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===wa(a))return b=a,!0});return b}function f(b){if(b){b.scrollIntoView();var c;c=h.yOffset;x(c)?c=c():sc(c)?(c=c[0],c="fixed"!==a.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):V(c)||(c=0);c&&(b=b.getBoundingClientRect().top,
|
||||
a.scrollBy(0,b-c))}else a.scrollTo(0,0)}function h(a){a=G(a)?a:c.hash();var b;a?(b=g.getElementById(a))?f(b):(b=e(g.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var g=a.document;b&&d.$watch(function(){return c.hash()},function(a,b){a===b&&""===a||Gf(function(){d.$evalAsync(h)})});return h}]}function ib(b,a){if(!b&&!a)return"";if(!b)return a;if(!a)return b;J(b)&&(b=b.join(" "));J(a)&&(a=a.join(" "));return b+" "+a}function Jf(b){G(b)&&(b=b.split(" "));var a=fa();m(b,function(b){b.length&&
|
||||
(a[b]=!0)});return a}function Ia(b){return C(b)?b:{}}function Kf(b,a,c,d){function e(a){try{a.apply(null,ua.call(arguments,1))}finally{if(E--,0===E)for(;K.length;)try{K.pop()()}catch(b){c.error(b)}}}function f(){ia=null;h();g()}function h(){a:{try{u=n.state;break a}catch(a){}u=void 0}u=v(u)?null:u;ka(u,L)&&(u=L);L=u}function g(){if(z!==l.url()||q!==u)z=l.url(),q=u,m(O,function(a){a(l.url(),u)})}var l=this,k=b.location,n=b.history,p=b.setTimeout,r=b.clearTimeout,t={};l.isMock=!1;var E=0,K=[];l.$$completeOutstandingRequest=
|
||||
e;l.$$incOutstandingRequestCount=function(){E++};l.notifyWhenNoOutstandingRequests=function(a){0===E?a():K.push(a)};var u,q,z=k.href,N=a.find("base"),ia=null;h();q=u;l.url=function(a,c,e){v(e)&&(e=null);k!==b.location&&(k=b.location);n!==b.history&&(n=b.history);if(a){var f=q===e;if(z===a&&(!d.history||f))return l;var g=z&&Ja(z)===Ja(a);z=a;q=e;if(!d.history||g&&f){if(!g||ia)ia=a;c?k.replace(a):g?(c=k,e=a.indexOf("#"),e=-1===e?"":a.substr(e),c.hash=e):k.href=a;k.href!==a&&(ia=a)}else n[c?"replaceState":
|
||||
"pushState"](e,"",a),h(),q=u;return l}return ia||k.href.replace(/%27/g,"'")};l.state=function(){return u};var O=[],H=!1,L=null;l.onUrlChange=function(a){if(!H){if(d.history)B(b).on("popstate",f);B(b).on("hashchange",f);H=!0}O.push(a);return a};l.$$applicationDestroyed=function(){B(b).off("hashchange popstate",f)};l.$$checkUrlChange=g;l.baseHref=function(){var a=N.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};l.defer=function(a,b){var c;E++;c=p(function(){delete t[c];e(a)},b||0);
|
||||
t[c]=!0;return c};l.defer.cancel=function(a){return t[a]?(delete t[a],r(a),e(y),!0):!1}}function Ve(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new Kf(b,d,a,c)}]}function We(){this.$get=function(){function b(b,d){function e(a){a!=p&&(r?r==a&&(r=a.n):r=a,f(a.n,a.p),f(a,p),p=a,p.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(b in a)throw I("$cacheFactory")("iid",b);var h=0,g=P({},d,{id:b}),l={},k=d&&d.capacity||Number.MAX_VALUE,n={},p=null,r=null;return a[b]=
|
||||
{put:function(a,b){if(!v(b)){if(k<Number.MAX_VALUE){var c=n[a]||(n[a]={key:a});e(c)}a in l||h++;l[a]=b;h>k&&this.remove(r.key);return b}},get:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;e(b)}return l[a]},remove:function(a){if(k<Number.MAX_VALUE){var b=n[a];if(!b)return;b==p&&(p=b.p);b==r&&(r=b.n);f(b.n,b.p);delete n[a]}delete l[a];h--},removeAll:function(){l={};h=0;n={};p=r=null},destroy:function(){n=g=l=null;delete a[b]},info:function(){return P({},g,{size:h})}}}var a={};b.info=function(){var b=
|
||||
{};m(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function rf(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Dc(b,a){function c(a,b,c){var d=/^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/,e={};m(a,function(a,f){var g=a.match(d);if(!g)throw ga("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f}});return e}function d(a){var b=a.charAt(0);if(!b||
|
||||
b!==F(b))throw ga("baddir",a);if(a!==a.trim())throw ga("baddir",a);}var e={},f=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,h=/(([\w\-]+)(?:\:([^;]+))?;?)/,g=Wd("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,k=/^(on[a-z]+|formaction)$/;this.directive=function r(a,f){Ta(a,"directive");G(a)?(d(a),qb(f,"directiveFactory"),e.hasOwnProperty(a)||(e[a]=[],b.factory(a+"Directive",["$injector","$exceptionHandler",function(b,d){var f=[];m(e[a],function(e,g){try{var h=b.invoke(e);x(h)?h={compile:qa(h)}:
|
||||
!h.compile&&h.link&&(h.compile=qa(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||a;h.require=h.require||h.controller&&h.name;h.restrict=h.restrict||"EA";var k=h,l=h,r=h.name,n={isolateScope:null,bindToController:null};C(l.scope)&&(!0===l.bindToController?(n.bindToController=c(l.scope,r,!0),n.isolateScope={}):n.isolateScope=c(l.scope,r,!1));C(l.bindToController)&&(n.bindToController=c(l.bindToController,r,!0));if(C(n.bindToController)){var S=l.controller,E=l.controllerAs;if(!S)throw ga("noctrl",
|
||||
r);var ca;a:if(E&&G(E))ca=E;else{if(G(S)){var m=Vc.exec(S);if(m){ca=m[3];break a}}ca=void 0}if(!ca)throw ga("noident",r);}var s=k.$$bindings=n;C(s.isolateScope)&&(h.$$isolateBindings=s.isolateScope);h.$$moduleName=e.$$moduleName;f.push(h)}catch(w){d(w)}});return f}])),e[a].push(f)):m(a,oc(r));return this};this.aHrefSanitizationWhitelist=function(b){return A(b)?(a.aHrefSanitizationWhitelist(b),this):a.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=function(b){return A(b)?(a.imgSrcSanitizationWhitelist(b),
|
||||
this):a.imgSrcSanitizationWhitelist()};var n=!0;this.debugInfoEnabled=function(a){return A(a)?(n=a,this):n};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$document","$sce","$animate","$$sanitizeUri",function(a,b,c,d,u,q,z,N,ia,O,H){function L(a,b){try{a.addClass(b)}catch(c){}}function W(a,b,c,d,e){a instanceof B||(a=B(a));m(a,function(b,c){b.nodeType==Pa&&b.nodeValue.match(/\S+/)&&(a[c]=B(b).wrap("<span></span>").parent()[0])});var f=
|
||||
S(a,b,a,c,d,e);W.$$addScopeClass(a);var g=null;return function(b,c,d){qb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==wa(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?B(Xb(g,B("<div>").append(a).html())):c?Ra.clone.call(a):a;if(h)for(var k in h)d.data("$"+k+"Controller",h[k].instance);W.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function S(a,
|
||||
b,c,d,e,f){function g(a,c,d,e){var f,k,l,r,n,t,O;if(q)for(O=Array(c.length),r=0;r<h.length;r+=3)f=h[r],O[f]=c[f];else O=c;r=0;for(n=h.length;r<n;)if(k=O[h[r++]],c=h[r++],f=h[r++],c){if(c.scope){if(l=a.$new(),W.$$addScopeInfo(B(k),l),t=c.$$destroyBindings)c.$$destroyBindings=null,l.$on("$destroyed",t)}else l=a;t=c.transcludeOnThisElement?ba(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?ba(a,b):null;c(f,l,k,d,t,c)}else f&&f(a,k.childNodes,w,e)}for(var h=[],k,l,r,n,q,t=0;t<a.length;t++){k=new Z;
|
||||
l=ca(a[t],[],k,0===t?d:w,e);(f=l.length?D(l,a[t],k,b,c,null,[],[],f):null)&&f.scope&&W.$$addScopeClass(k.$$element);k=f&&f.terminal||!(r=a[t].childNodes)||!r.length?null:S(r,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(t,f,k),n=!0,q=q||f;f=null}return n?g:null}function ba(a,b,c){return function(d,e,f,g,h){d||(d=a.$new(!1,h),d.$$transcluded=!0);return b(d,e,{parentBoundTranscludeFn:c,transcludeControllers:f,futureParentElement:g})}}function ca(a,b,c,d,e){var g=
|
||||
c.$attr,k;switch(a.nodeType){case pa:na(b,ya(wa(a)),"E",d,e);for(var l,r,n,q=a.attributes,t=0,O=q&&q.length;t<O;t++){var K=!1,H=!1;l=q[t];k=l.name;r=T(l.value);l=ya(k);if(n=ja.test(l))k=k.replace(Wc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});var S=l.replace(/(Start|End)$/,"");I(S)&&l===S+"Start"&&(K=k,H=k.substr(0,k.length-5)+"end",k=k.substr(0,k.length-6));l=ya(k.toLowerCase());g[l]=k;if(n||!c.hasOwnProperty(l))c[l]=r,Rc(a,l)&&(c[l]=!0);V(a,b,r,l,n);na(b,l,"A",d,e,K,H)}a=
|
||||
a.className;C(a)&&(a=a.animVal);if(G(a)&&""!==a)for(;k=h.exec(a);)l=ya(k[2]),na(b,l,"C",d,e)&&(c[l]=T(k[3])),a=a.substr(k.index+k[0].length);break;case Pa:if(11===Wa)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Pa;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);Ka(b,a.nodeValue);break;case 8:try{if(k=f.exec(a.nodeValue))l=ya(k[1]),na(b,l,"M",d,e)&&(c[l]=T(k[2]))}catch(E){}}b.sort(M);return b}function za(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ga("uterdir",
|
||||
b,c);a.nodeType==pa&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return B(d)}function s(a,b,c){return function(d,e,f,g,h){e=za(e[0],b,c);return a(d,e,f,g,h)}}function D(a,b,d,e,f,g,h,k,r){function n(a,b,c,d){if(a){c&&(a=s(a,c,d));a.require=D.require;a.directiveName=y;if(u===D||D.$$isolateScope)a=$(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=s(b,c,d));b.require=D.require;b.directiveName=y;if(u===D||D.$$isolateScope)b=$(b,{isolateScope:!0});k.push(b)}}
|
||||
function t(a,b,c,d){var e;if(G(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;e||(d="$"+b+"Controller",e=g?c.inheritedData(d):c.data(d));if(!e&&!f)throw ga("ctreq",b,a);}else if(J(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=t(a,b[g],c,d);return e||null}function O(a,b,c,d,e,f){var g=fa(),h;for(h in d){var k=d[h],l={$scope:k===u||k.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},r=k.controller;"@"==r&&(r=b[k.name]);l=q(r,
|
||||
l,!0,k.controllerAs);g[k.name]=l;ia||a.data("$"+k.name+"Controller",l.instance)}return g}function K(a,c,e,f,g,l){function r(a,b,c){var d;ab(a)||(c=b,b=a,a=w);ia&&(d=ca);c||(c=ia?N.parent():N);return g(a,b,d,c,za)}var n,q,H,E,ca,z,N;b===e?(f=d,N=d.$$element):(N=B(e),f=new Z(N,d));u&&(E=c.$new(!0));g&&(z=r,z.$$boundTransclude=g);ba&&(ca=O(N,f,z,ba,E,c));u&&(W.$$addScopeInfo(N,E,!0,!(L&&(L===u||L===u.$$originalDirective))),W.$$addScopeClass(N,!0),E.$$isolateBindings=u.$$isolateBindings,Y(c,f,E,E.$$isolateBindings,
|
||||
u,E));if(ca){var Va=u||S,m;Va&&ca[Va.name]&&(q=Va.$$bindings.bindToController,(H=ca[Va.name])&&H.identifier&&q&&(m=H,l.$$destroyBindings=Y(c,f,H.instance,q,Va)));for(n in ca){H=ca[n];var D=H();D!==H.instance&&(H.instance=D,N.data("$"+n+"Controller",D),H===m&&(l.$$destroyBindings(),l.$$destroyBindings=Y(c,f,D,q,Va)))}}n=0;for(l=h.length;n<l;n++)q=h[n],aa(q,q.isolateScope?E:c,N,f,q.require&&t(q.directiveName,q.require,N,ca),z);var za=c;u&&(u.template||null===u.templateUrl)&&(za=E);a&&a(za,e.childNodes,
|
||||
w,g);for(n=k.length-1;0<=n;n--)q=k[n],aa(q,q.isolateScope?E:c,N,f,q.require&&t(q.directiveName,q.require,N,ca),z)}r=r||{};for(var H=-Number.MAX_VALUE,S=r.newScopeDirective,ba=r.controllerDirectives,u=r.newIsolateScopeDirective,L=r.templateDirective,z=r.nonTlbTranscludeDirective,N=!1,m=!1,ia=r.hasElementTranscludeDirective,v=d.$$element=B(b),D,y,M,Ka=e,na,I=0,F=a.length;I<F;I++){D=a[I];var P=D.$$start,R=D.$$end;P&&(v=za(b,P,R));M=w;if(H>D.priority)break;if(M=D.scope)D.templateUrl||(C(M)?(Q("new/isolated scope",
|
||||
u||S,D,v),u=D):Q("new/isolated scope",u,D,v)),S=S||D;y=D.name;!D.templateUrl&&D.controller&&(M=D.controller,ba=ba||fa(),Q("'"+y+"' controller",ba[y],D,v),ba[y]=D);if(M=D.transclude)N=!0,D.$$tlb||(Q("transclusion",z,D,v),z=D),"element"==M?(ia=!0,H=D.priority,M=v,v=d.$$element=B(X.createComment(" "+y+": "+d[y]+" ")),b=v[0],U(f,ua.call(M,0),b),Ka=W(M,e,H,g&&g.name,{nonTlbTranscludeDirective:z})):(M=B(Ub(b)).contents(),v.empty(),Ka=W(M,e));if(D.template)if(m=!0,Q("template",L,D,v),L=D,M=x(D.template)?
|
||||
D.template(v,d):D.template,M=ha(M),D.replace){g=D;M=Sb.test(M)?Xc(Xb(D.templateNamespace,T(M))):[];b=M[0];if(1!=M.length||b.nodeType!==pa)throw ga("tplrt",y,"");U(f,v,b);F={$attr:{}};M=ca(b,[],F);var Lf=a.splice(I+1,a.length-(I+1));u&&A(M);a=a.concat(M).concat(Lf);Yc(d,F);F=a.length}else v.html(M);if(D.templateUrl)m=!0,Q("template",L,D,v),L=D,D.replace&&(g=D),K=Mf(a.splice(I,a.length-I),v,d,f,N&&Ka,h,k,{controllerDirectives:ba,newScopeDirective:S!==D&&S,newIsolateScopeDirective:u,templateDirective:L,
|
||||
nonTlbTranscludeDirective:z}),F=a.length;else if(D.compile)try{na=D.compile(v,d,Ka),x(na)?n(null,na,P,R):na&&n(na.pre,na.post,P,R)}catch(V){c(V,xa(v))}D.terminal&&(K.terminal=!0,H=Math.max(H,D.priority))}K.scope=S&&!0===S.scope;K.transcludeOnThisElement=N;K.templateOnThisElement=m;K.transclude=Ka;r.hasElementTranscludeDirective=ia;return K}function A(a){for(var b=0,c=a.length;b<c;b++)a[b]=Nb(a[b],{$$isolateScope:!0})}function na(b,d,f,g,h,k,l){if(d===h)return null;h=null;if(e.hasOwnProperty(d)){var n;
|
||||
d=a.get(d+"Directive");for(var q=0,t=d.length;q<t;q++)try{n=d[q],(v(g)||g>n.priority)&&-1!=n.restrict.indexOf(f)&&(k&&(n=Nb(n,{$$start:k,$$end:l})),b.push(n),h=n)}catch(H){c(H)}}return h}function I(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d<f;d++)if(b=c[d],b.multiElement)return!0;return!1}function Yc(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,function(b,f){"class"==
|
||||
f?(L(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):"style"==f?(e.attr("style",e.attr("style")+";"+b),a.style=(a.style?a.style+";":"")+b):"$"==f.charAt(0)||a.hasOwnProperty(f)||(a[f]=b,d[f]=c[f])})}function Mf(a,b,c,e,f,g,h,k){var l=[],r,n,q=b[0],t=a.shift(),H=Nb(t,{templateUrl:null,transclude:null,replace:null,$$originalDirective:t}),O=x(t.templateUrl)?t.templateUrl(b,c):t.templateUrl,E=t.templateNamespace;b.empty();d(O).then(function(d){var K,u;d=ha(d);if(t.replace){d=Sb.test(d)?Xc(Xb(E,T(d))):
|
||||
[];K=d[0];if(1!=d.length||K.nodeType!==pa)throw ga("tplrt",t.name,O);d={$attr:{}};U(e,b,K);var z=ca(K,[],d);C(t.scope)&&A(z);a=z.concat(a);Yc(c,d)}else K=q,b.html(d);a.unshift(H);r=D(a,K,c,f,b,t,g,h,k);m(e,function(a,c){a==K&&(e[c]=b[0])});for(n=S(b[0].childNodes,f);l.length;){d=l.shift();u=l.shift();var N=l.shift(),W=l.shift(),z=b[0];if(!d.$$destroyed){if(u!==q){var za=u.className;k.hasElementTranscludeDirective&&t.replace||(z=Ub(K));U(N,B(u),z);L(B(z),za)}u=r.transcludeOnThisElement?ba(d,r.transclude,
|
||||
W):W;r(n,d,z,e,u,r)}}l=null});return function(a,b,c,d,e){a=e;b.$$destroyed||(l?l.push(b,c,d,a):(r.transcludeOnThisElement&&(a=ba(b,r.transclude,e)),r(n,b,c,d,a,r)))}}function M(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function Q(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ga("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,xa(d));}function Ka(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=
|
||||
a.parent();var b=!!a.length;b&&W.$$addBindingClass(a);return function(a,c){var e=c.parent();b||W.$$addBindingClass(e);W.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function Xb(a,b){a=F(a||"html");switch(a){case "svg":case "math":var c=X.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function R(a,b){if("srcdoc"==b)return ia.HTML;var c=wa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||
|
||||
"ngSrc"==b))return ia.RESOURCE_URL}function V(a,c,d,e,f){var h=R(a,e);f=g[e]||f;var l=b(d,!0,h,f);if(l){if("multiple"===e&&"select"===wa(a))throw ga("selmulti",xa(a));c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers=fa());if(k.test(e))throw ga("nodomevents");var r=g[e];r!==d&&(l=r&&b(r,!0,h,f),d=r);l&&(g[e]=l(a),(c[e]||(c[e]=[])).$$inter=!0,(g.$$observers&&g.$$observers[e].$$scope||a).$watch(l,function(a,b){"class"===e&&a!=b?g.$updateClass(a,b):g.$set(e,
|
||||
a)}))}}}})}}function U(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=X.createDocumentFragment();a.appendChild(d);B.hasData(d)&&(B(c).data(B(d).data()),ra?(Qb=!0,ra.cleanData([d])):delete B.cache[d[B.expando]]);d=1;for(e=b.length;d<e;d++)f=b[d],B(f).remove(),a.appendChild(f),delete b[d];b[0]=c;b.length=1}function $(a,
|
||||
b){return P(function(){return a.apply(null,arguments)},a,b)}function aa(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,xa(d))}}function Y(a,c,d,e,f,g){var h;m(e,function(e,g){var k=e.attrName,l=e.optional,r,n,q,K;switch(e.mode){case "@":l||ta.call(c,k)||(d[g]=c[k]=void 0);c.$observe(k,function(a){G(a)&&(d[g]=a)});c.$$observers[k].$$scope=a;G(c[k])&&(d[g]=b(c[k])(a));break;case "=":if(!ta.call(c,k)){if(l)break;c[k]=void 0}if(l&&!c[k])break;n=u(c[k]);K=n.literal?ka:function(a,b){return a===b||a!==a&&b!==
|
||||
b};q=n.assign||function(){r=d[g]=n(a);throw ga("nonassign",c[k],f.name);};r=d[g]=n(a);l=function(b){K(b,d[g])||(K(b,r)?q(a,b=d[g]):d[g]=b);return r=b};l.$stateful=!0;l=e.collection?a.$watchCollection(c[k],l):a.$watch(u(c[k],l),null,n.literal);h=h||[];h.push(l);break;case "&":n=c.hasOwnProperty(k)?u(c[k]):y;if(n===y&&l)break;d[g]=function(b){return n(a,b)}}});e=h?function(){for(var a=0,b=h.length;a<b;++a)h[a]()}:y;return g&&e!==y?(g.$on("$destroy",e),y):e}var Z=function(a,b){if(b){var c=Object.keys(b),
|
||||
d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a};Z.prototype={$normalize:ya,$addClass:function(a){a&&0<a.length&&O.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&O.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=Zc(a,b);c&&c.length&&O.addClass(this.$$element,c);(c=Zc(b,a))&&c.length&&O.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=Rc(this.$$element[0],a),g=$c[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=
|
||||
b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Ac(a,"-"));f=wa(this.$$element);if("a"===f&&"href"===a||"img"===f&&"src"===a)this[a]=b=H(b,"src"===a);else if("img"===f&&"srcset"===a){for(var f="",g=T(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(g)?k:/(,)/,g=g.split(k),k=Math.floor(g.length/2),l=0;l<k;l++)var r=2*l,f=f+H(T(g[r]),!0),f=f+(" "+T(g[r+1]));g=T(g[2*l]).split(/\s/);f+=H(T(g[0]),!0);2===g.length&&(f+=" "+T(g[1]));this[a]=b=f}!1!==d&&(null===b||v(b)?this.$$element.removeAttr(e):
|
||||
this.$$element.attr(e,b));(a=this.$$observers)&&m(a[h],function(a){try{a(b)}catch(d){c(d)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=fa()),e=d[a]||(d[a]=[]);e.push(b);z.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||v(c[a])||b(c[a])});return function(){cb(e,b)}}};var da=b.startSymbol(),ea=b.endSymbol(),ha="{{"==da||"}}"==ea?$a:function(a){return a.replace(/\{\{/g,da).replace(/}}/g,ea)},ja=/^ngAttr[A-Z]/;W.$$addBindingInfo=n?function(a,b){var c=a.data("$binding")||
|
||||
[];J(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:y;W.$$addBindingClass=n?function(a){L(a,"ng-binding")}:y;W.$$addScopeInfo=n?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:y;W.$$addScopeClass=n?function(a,b){L(a,b?"ng-isolate-scope":"ng-scope")}:y;return W}]}function ya(b){return gb(b.replace(Wc,""))}function Zc(b,a){var c="",d=b.split(/\s+/),e=a.split(/\s+/),f=0;a:for(;f<d.length;f++){for(var h=d[f],g=0;g<e.length;g++)if(h==e[g])continue a;c+=(0<c.length?
|
||||
" ":"")+h}return c}function Xc(b){b=B(b);var a=b.length;if(1>=a)return b;for(;a--;)8===b[a].nodeType&&Nf.call(b,a,1);return b}function Xe(){var b={},a=!1;this.register=function(a,d){Ta(a,"controller");C(a)?P(b,a):b[a]=d};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(c,d){function e(a,b,c,d){if(!a||!C(a.$scope))throw I("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,h,g,l){var k,n,p;g=!0===g;l&&G(l)&&(p=l);if(G(f)){l=f.match(Vc);if(!l)throw Of("ctrlfmt",f);
|
||||
n=l[1];p=p||l[3];f=b.hasOwnProperty(n)?b[n]:Cc(h.$scope,n,!0)||(a?Cc(d,n,!0):w);Sa(f,n,!0)}if(g)return g=(J(f)?f[f.length-1]:f).prototype,k=Object.create(g||null),p&&e(h,p,k,n||f.name),P(function(){var a=c.invoke(f,k,h,n);a!==k&&(C(a)||x(a))&&(k=a,p&&e(h,p,k,n||f.name));return k},{instance:k,identifier:p});k=c.instantiate(f,h,n);p&&e(h,p,k,n||f.name);return k}}]}function Ye(){this.$get=["$window",function(b){return B(b.document)}]}function Ze(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,
|
||||
arguments)}}]}function Yb(b){return C(b)?ea(b)?b.toISOString():eb(b):b}function df(){this.$get=function(){return function(b){if(!b)return"";var a=[];nc(b,function(b,d){null===b||v(b)||(J(b)?m(b,function(b,c){a.push(la(d)+"="+la(Yb(b)))}):a.push(la(d)+"="+la(Yb(b))))});return a.join("&")}}}function ef(){this.$get=function(){return function(b){function a(b,e,f){null===b||v(b)||(J(b)?m(b,function(b,c){a(b,e+"["+(C(b)?c:"")+"]")}):C(b)&&!ea(b)?nc(b,function(b,c){a(b,e+(f?"":"[")+c+(f?"":"]"))}):c.push(la(e)+
|
||||
"="+la(Yb(b))))}if(!b)return"";var c=[];a(b,"",!0);return c.join("&")}}}function Zb(b,a){if(G(b)){var c=b.replace(Pf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(ad))||(d=(d=c.match(Qf))&&Rf[d[0]].test(c));d&&(b=vc(c))}}return b}function bd(b){var a=fa(),c;G(b)?m(b.split("\n"),function(b){c=b.indexOf(":");var e=F(T(b.substr(0,c)));b=T(b.substr(c+1));e&&(a[e]=a[e]?a[e]+", "+b:b)}):C(b)&&m(b,function(b,c){var f=F(c),h=T(b);f&&(a[f]=a[f]?a[f]+", "+h:h)});return a}function cd(b){var a;
|
||||
return function(c){a||(a=bd(b));return c?(c=a[F(c)],void 0===c&&(c=null),c):a}}function dd(b,a,c,d){if(x(d))return d(b,a,c);m(d,function(d){b=d(b,a,c)});return b}function cf(){var b=this.defaults={transformResponse:[Zb],transformRequest:[function(a){return C(a)&&"[object File]"!==va.call(a)&&"[object Blob]"!==va.call(a)&&"[object FormData]"!==va.call(a)?eb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ja($b),put:ja($b),patch:ja($b)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",
|
||||
paramSerializer:"$httpParamSerializer"},a=!1;this.useApplyAsync=function(b){return A(b)?(a=!!b,this):a};var c=!0;this.useLegacyPromiseExtensions=function(a){return A(a)?(c=!!a,this):c};var d=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,h,g,l,k){function n(a){function d(a){var b=P({},a);b.data=a.data?dd(a.data,a.headers,a.status,f.transformResponse):a.data;a=a.status;return 200<=a&&300>a?b:l.reject(b)}function e(a,b){var c,
|
||||
d={};m(a,function(a,e){x(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!da.isObject(a))throw I("$http")("badreq",a);var f=P({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse,paramSerializer:b.paramSerializer},a);f.headers=function(a){var c=b.headers,d=P({},a.headers),f,g,h,c=P({},c.common,c[F(a.method)]);a:for(f in c){g=F(f);for(h in d)if(F(h)===g)continue a;d[f]=c[f]}return e(d,ja(a))}(a);f.method=sb(f.method);f.paramSerializer=G(f.paramSerializer)?k.get(f.paramSerializer):
|
||||
f.paramSerializer;var g=[function(a){var c=a.headers,e=dd(a.data,cd(c),w,a.transformRequest);v(e)&&m(c,function(a,b){"content-type"===F(b)&&delete c[b]});v(a.withCredentials)&&!v(b.withCredentials)&&(a.withCredentials=b.withCredentials);return p(a,e).then(d,d)},w],h=l.when(f);for(m(E,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){a=g.shift();var r=g.shift(),h=h.then(a,r)}c?(h.success=function(a){Sa(a,
|
||||
"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Sa(a,"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=ed("success"),h.error=ed("error"));return h}function p(c,d){function h(b,c,d,e){function f(){k(c,b,d,e)}L&&(200<=b&&300>b?L.put(ba,[b,c,bd(d),e]):L.remove(ba));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function k(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?O.resolve:O.reject)({data:a,status:b,headers:cd(d),config:c,statusText:e})}
|
||||
function p(a){k(a.data,a.status,ja(a.headers()),a.statusText)}function E(){var a=n.pendingRequests.indexOf(c);-1!==a&&n.pendingRequests.splice(a,1)}var O=l.defer(),H=O.promise,L,m,S=c.headers,ba=r(c.url,c.paramSerializer(c.params));n.pendingRequests.push(c);H.then(E,E);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(L=C(c.cache)?c.cache:C(b.cache)?b.cache:t);L&&(m=L.get(ba),A(m)?m&&x(m.then)?m.then(p,p):J(m)?k(m[1],m[0],ja(m[2]),m[3]):k(m,200,{},"OK"):L.put(ba,H));v(m)&&((m=
|
||||
fd(c.url)?f()[c.xsrfCookieName||b.xsrfCookieName]:w)&&(S[c.xsrfHeaderName||b.xsrfHeaderName]=m),e(c.method,ba,d,h,S,c.timeout,c.withCredentials,c.responseType));return H}function r(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var t=h("$http");b.paramSerializer=G(b.paramSerializer)?k.get(b.paramSerializer):b.paramSerializer;var E=[];m(d,function(a){E.unshift(G(a)?k.get(a):k.invoke(a))});n.pendingRequests=[];(function(a){m(arguments,function(a){n[a]=function(b,c){return n(P({},c||{},
|
||||
{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){n[a]=function(b,c,d){return n(P({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");n.defaults=b;return n}]}function gf(){this.$get=function(){return function(){return new Q.XMLHttpRequest}}}function ff(){this.$get=["$browser","$window","$document","$xhrFactory",function(b,a,c,d){return Sf(b,d,b.defer,a.angular.callbacks,c[0])}]}function Sf(b,a,c,d,e){function f(a,b,c){var f=e.createElement("script"),
|
||||
n=null;f.type="text/javascript";f.src=a;f.async=!0;n=function(a){f.removeEventListener("load",n,!1);f.removeEventListener("error",n,!1);e.body.removeChild(f);f=null;var h=-1,t="unknown";a&&("load"!==a.type||d[b].called||(a={type:"error"}),t=a.type,h="error"===a.type?404:200);c&&c(h,t)};f.addEventListener("load",n,!1);f.addEventListener("error",n,!1);e.body.appendChild(f);return n}return function(e,g,l,k,n,p,r,t){function E(){q&&q();z&&z.abort()}function K(a,d,e,f,g){A(s)&&c.cancel(s);q=z=null;a(d,
|
||||
e,f,g);b.$$completeOutstandingRequest(y)}b.$$incOutstandingRequestCount();g=g||b.url();if("jsonp"==F(e)){var u="_"+(d.counter++).toString(36);d[u]=function(a){d[u].data=a;d[u].called=!0};var q=f(g.replace("JSON_CALLBACK","angular.callbacks."+u),u,function(a,b){K(k,a,d[u].data,"",b);d[u]=y})}else{var z=a(e,g);z.open(e,g,!0);m(n,function(a,b){A(a)&&z.setRequestHeader(b,a)});z.onload=function(){var a=z.statusText||"",b="response"in z?z.response:z.responseText,c=1223===z.status?204:z.status;0===c&&(c=
|
||||
b?200:"file"==Aa(g).protocol?404:0);K(k,c,b,z.getAllResponseHeaders(),a)};e=function(){K(k,-1,null,null,"")};z.onerror=e;z.onabort=e;r&&(z.withCredentials=!0);if(t)try{z.responseType=t}catch(N){if("json"!==t)throw N;}z.send(v(l)?null:l)}if(0<p)var s=c(E,p);else p&&x(p.then)&&p.then(E)}}function af(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler","$sce",function(c,d,e){function f(a){return"\\\\\\"+
|
||||
a}function h(c){return c.replace(n,b).replace(p,a)}function g(f,g,n,p){function u(a){try{var b=a;a=n?e.getTrusted(n,b):e.valueOf(b);var c;if(p&&!A(a))c=a;else if(null==a)c="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=eb(a)}c=a}return c}catch(g){d(La.interr(f,g))}}p=!!p;for(var q,m,N=0,s=[],O=[],H=f.length,L=[],W=[];N<H;)if(-1!=(q=f.indexOf(b,N))&&-1!=(m=f.indexOf(a,q+l)))N!==q&&L.push(h(f.substring(N,q))),N=f.substring(q+l,m),s.push(N),O.push(c(N,u)),N=m+k,W.push(L.length),
|
||||
L.push("");else{N!==H&&L.push(h(f.substring(N)));break}n&&1<L.length&&La.throwNoconcat(f);if(!g||s.length){var S=function(a){for(var b=0,c=s.length;b<c;b++){if(p&&v(a[b]))return;L[W[b]]=a[b]}return L.join("")};return P(function(a){var b=0,c=s.length,e=Array(c);try{for(;b<c;b++)e[b]=O[b](a);return S(e)}catch(g){d(La.interr(f,g))}},{exp:f,expressions:s,$$watchDelegate:function(a,b){var c;return a.$watchGroup(O,function(d,e){var f=S(d);x(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=b.length,k=a.length,
|
||||
n=new RegExp(b.replace(/./g,f),"g"),p=new RegExp(a.replace(/./g,f),"g");g.startSymbol=function(){return b};g.endSymbol=function(){return a};return g}]}function bf(){this.$get=["$rootScope","$window","$q","$$q",function(b,a,c,d){function e(e,g,l,k){var n=4<arguments.length,p=n?ua.call(arguments,4):[],r=a.setInterval,t=a.clearInterval,E=0,K=A(k)&&!k,u=(K?d:c).defer(),q=u.promise;l=A(l)?l:0;q.then(null,null,n?function(){e.apply(null,p)}:e);q.$$intervalId=r(function(){u.notify(E++);0<l&&E>=l&&(u.resolve(E),
|
||||
t(q.$$intervalId),delete f[q.$$intervalId]);K||b.$apply()},g);f[q.$$intervalId]=u;return q}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete f[b.$$intervalId],!0):!1};return e}]}function ac(b){b=b.split("/");for(var a=b.length;a--;)b[a]=ob(b[a]);return b.join("/")}function gd(b,a){var c=Aa(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=Y(c.port)||Tf[c.protocol]||null}function hd(b,a){var c="/"!==b.charAt(0);
|
||||
c&&(b="/"+b);var d=Aa(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)?d.pathname.substring(1):d.pathname);a.$$search=yc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function sa(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ja(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function Cb(b){return b.replace(/(#.+)|#$/,"$1")}function bc(b,a,c){this.$$html5=!0;c=c||"";gd(b,this);this.$$parse=function(b){var c=sa(a,
|
||||
b);if(!G(c))throw Db("ipthprfx",b,a);hd(c,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var b=Pb(this.$$search),c=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=ac(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=a+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,h;A(f=sa(b,d))?(h=f,h=A(f=sa(c,f))?a+(sa("/",f)||f):b+h):A(f=sa(a,d))?h=a+f:a==d+"/"&&(h=a);h&&this.$$parse(h);return!!h}}function cc(b,a,c){gd(b,this);
|
||||
this.$$parse=function(d){var e=sa(b,d)||sa(a,d),f;v(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",v(e)&&(b=d,this.replace())):(f=sa(c,e),v(f)&&(f=e));hd(f,this);d=this.$$path;var e=b,h=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));h.exec(f)||(d=(f=h.exec(d))?f[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var a=Pb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=ac(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+(this.$$url?c+this.$$url:"")};this.$$parseLinkUrl=
|
||||
function(a,c){return Ja(b)==Ja(a)?(this.$$parse(a),!0):!1}}function id(b,a,c){this.$$html5=!0;cc.apply(this,arguments);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,h;b==Ja(d)?f=d:(h=sa(a,d))?f=b+c+h:a===d+"/"&&(f=a);f&&this.$$parse(f);return!!f};this.$$compose=function(){var a=Pb(this.$$search),e=this.$$hash?"#"+ob(this.$$hash):"";this.$$url=ac(this.$$path)+(a?"?"+a:"")+e;this.$$absUrl=b+c+this.$$url}}function Eb(b){return function(){return this[b]}}function jd(b,
|
||||
a){return function(c){if(v(c))return this[b];this[b]=a(c);this.$$compose();return this}}function hf(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return A(a)?(b=a,this):b};this.html5Mode=function(b){return bb(b)?(a.enabled=b,this):C(b)?(bb(b.enabled)&&(a.enabled=b.enabled),bb(b.requireBase)&&(a.requireBase=b.requireBase),bb(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,
|
||||
d,e,f,h){function g(a,b,c){var e=k.url(),f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,n;n=d.baseHref();var p=d.url(),r;if(a.enabled){if(!n&&a.requireBase)throw Db("nobase");r=p.substring(0,p.indexOf("/",p.indexOf("//")+2))+(n||"/");n=e.history?bc:id}else r=Ja(p),n=cc;var t=r.substr(0,Ja(r).lastIndexOf("/")+1);k=new n(r,t,"#"+b);k.$$parseLinkUrl(p,p);k.$$state=d.state();
|
||||
var E=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&&!b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=B(b.target);"a"!==wa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var g=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");C(g)&&"[object SVGAnimatedString]"===g.toString()&&(g=Aa(g.animVal).href);E.test(g)||!g||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(g,l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),h.angular["ff-684208-preventDefault"]=
|
||||
!0))}});Cb(k.absUrl())!=Cb(p)&&d.url(k.absUrl(),!0);var K=!0;d.onUrlChange(function(a,b){v(sa(t,a))?h.location.href=a:(c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,g(d,!1,e)):(K=!1,l(d,e)))}),c.$$phase||c.$digest())});c.$watch(function(){var a=Cb(d.url()),b=Cb(k.absUrl()),f=d.state(),h=k.$$replace,r=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(K||r)K=!1,
|
||||
c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()===b&&(d?(k.$$parse(a),k.$$state=f):(r&&g(b,h,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function jf(){var b=!0,a=this;this.debugEnabled=function(a){return A(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=
|
||||
a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||y;a=!1;try{a=!!e.apply}catch(l){}return a?function(){var a=[];m(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function Xa(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===
|
||||
b||"__proto__"===b)throw Z("isecfld",a);return b}function kd(b,a){b+="";if(!G(b))throw Z("iseccst",a);return b}function Ba(b,a){if(b){if(b.constructor===b)throw Z("isecfn",a);if(b.window===b)throw Z("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw Z("isecdom",a);if(b===Object)throw Z("isecobj",a);}return b}function ld(b,a){if(b){if(b.constructor===b)throw Z("isecfn",a);if(b===Uf||b===Vf||b===Wf)throw Z("isecff",a);}}function md(b,a){if(b&&(b===(0).constructor||b===(!1).constructor||
|
||||
b==="".constructor||b==={}.constructor||b===[].constructor||b===Function.constructor))throw Z("isecaf",a);}function Xf(b,a){return"undefined"!==typeof b?b:a}function nd(b,a){return"undefined"===typeof b?a:"undefined"===typeof a?b:b+a}function U(b,a){var c,d;switch(b.type){case s.Program:c=!0;m(b.body,function(b){U(b.expression,a);c=c&&b.expression.constant});b.constant=c;break;case s.Literal:b.constant=!0;b.toWatch=[];break;case s.UnaryExpression:U(b.argument,a);b.constant=b.argument.constant;b.toWatch=
|
||||
b.argument.toWatch;break;case s.BinaryExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.left.toWatch.concat(b.right.toWatch);break;case s.LogicalExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;b.toWatch=b.constant?[]:[b];break;case s.ConditionalExpression:U(b.test,a);U(b.alternate,a);U(b.consequent,a);b.constant=b.test.constant&&b.alternate.constant&&b.consequent.constant;b.toWatch=b.constant?[]:[b];break;case s.Identifier:b.constant=
|
||||
!1;b.toWatch=[b];break;case s.MemberExpression:U(b.object,a);b.computed&&U(b.property,a);b.constant=b.object.constant&&(!b.computed||b.property.constant);b.toWatch=[b];break;case s.CallExpression:c=b.filter?!a(b.callee.name).$stateful:!1;d=[];m(b.arguments,function(b){U(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=b.filter&&!a(b.callee.name).$stateful?d:[b];break;case s.AssignmentExpression:U(b.left,a);U(b.right,a);b.constant=b.left.constant&&b.right.constant;
|
||||
b.toWatch=[b];break;case s.ArrayExpression:c=!0;d=[];m(b.elements,function(b){U(b,a);c=c&&b.constant;b.constant||d.push.apply(d,b.toWatch)});b.constant=c;b.toWatch=d;break;case s.ObjectExpression:c=!0;d=[];m(b.properties,function(b){U(b.value,a);c=c&&b.value.constant;b.value.constant||d.push.apply(d,b.value.toWatch)});b.constant=c;b.toWatch=d;break;case s.ThisExpression:b.constant=!1,b.toWatch=[]}}function od(b){if(1==b.length){b=b[0].expression;var a=b.toWatch;return 1!==a.length?a:a[0]!==b?a:w}}
|
||||
function pd(b){return b.type===s.Identifier||b.type===s.MemberExpression}function qd(b){if(1===b.body.length&&pd(b.body[0].expression))return{type:s.AssignmentExpression,left:b.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function rd(b){return 0===b.body.length||1===b.body.length&&(b.body[0].expression.type===s.Literal||b.body[0].expression.type===s.ArrayExpression||b.body[0].expression.type===s.ObjectExpression)}function sd(b,a){this.astBuilder=b;this.$filter=a}function td(b,
|
||||
a){this.astBuilder=b;this.$filter=a}function Fb(b){return"constructor"==b}function dc(b){return x(b.valueOf)?b.valueOf():Yf.call(b)}function kf(){var b=fa(),a=fa();this.$get=["$filter",function(c){function d(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=dc(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function e(a,b,c,e,f){var g=e.inputs,h;if(1===g.length){var k=d,g=g[0];return a.$watch(function(a){var b=g(a);d(b,k)||(h=e(a,w,w,[b]),k=b&&dc(b));return h},b,c,f)}for(var l=[],n=[],p=0,
|
||||
m=g.length;p<m;p++)l[p]=d,n[p]=null;return a.$watch(function(a){for(var b=!1,c=0,f=g.length;c<f;c++){var k=g[c](a);if(b||(b=!d(k,l[c])))n[c]=k,l[c]=k&&dc(k)}b&&(h=e(a,w,w,n));return h},b,c,f)}function f(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;x(b)&&b.apply(this,arguments);A(a)&&d.$$postDigest(function(){A(f)&&e()})},c)}function h(a,b,c,d){function e(a){var b=!0;m(a,function(a){A(a)||(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a,
|
||||
c,d){g=a;x(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function g(a,b,c,d){var e;return e=a.$watch(function(a){return d(a)},function(a,c,d){x(b)&&b.apply(this,arguments);e()},c)}function l(a,b){if(!b)return a;var c=a.$$watchDelegate,c=c!==h&&c!==f?function(c,d,e,f){e=a(c,d,e,f);return b(e,c,d)}:function(c,d,e,f){e=a(c,d,e,f);c=b(e,c,d);return A(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==e?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=e,c.inputs=
|
||||
a.inputs?a.inputs:[a]);return c}var k=Fa().noUnsafeEval,n={csp:k,expensiveChecks:!1},p={csp:k,expensiveChecks:!0};return function(d,k,E){var m,u,q;switch(typeof d){case "string":q=d=d.trim();var s=E?a:b;m=s[q];m||(":"===d.charAt(0)&&":"===d.charAt(1)&&(u=!0,d=d.substring(2)),E=E?p:n,m=new ec(E),m=(new fc(m,c,E)).parse(d),m.constant?m.$$watchDelegate=g:u?m.$$watchDelegate=m.literal?h:f:m.inputs&&(m.$$watchDelegate=e),s[q]=m);return l(m,k);case "function":return l(d,k);default:return y}}}]}function mf(){this.$get=
|
||||
["$rootScope","$exceptionHandler",function(b,a){return ud(function(a){b.$evalAsync(a)},a)}]}function nf(){this.$get=["$browser","$exceptionHandler",function(b,a){return ud(function(a){b.defer(a)},a)}]}function ud(b,a){function c(a,b,c){function d(b){return function(c){e||(e=!0,b.call(a,c))}}var e=!1;return[d(b),d(c)]}function d(){this.$$state={status:0}}function e(a,b){return function(c){b.call(a,c)}}function f(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,b(function(){var b,d,e;e=c.pending;
|
||||
c.processScheduled=!1;c.pending=w;for(var f=0,g=e.length;f<g;++f){d=e[f][0];b=e[f][c.status];try{x(b)?d.resolve(b(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),a(h)}}}))}function h(){this.promise=new d;this.resolve=e(this,this.resolve);this.reject=e(this,this.reject);this.notify=e(this,this.notify)}var g=I("$q",TypeError);P(d.prototype,{then:function(a,b,c){if(v(a)&&v(b)&&v(c))return this;var d=new h;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,
|
||||
a,b,c]);0<this.$$state.status&&f(this.$$state);return d.promise},"catch":function(a){return this.then(null,a)},"finally":function(a,b){return this.then(function(b){return k(b,!0,a)},function(b){return k(b,!1,a)},b)}});P(h.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(g("qcycle",a)):this.$$resolve(a))},$$resolve:function(b){var d,e;e=c(this,this.$$resolve,this.$$reject);try{if(C(b)||x(b))d=b&&b.then;x(d)?(this.promise.$$state.status=-1,d.call(b,e[0],e[1],
|
||||
this.notify)):(this.promise.$$state.value=b,this.promise.$$state.status=1,f(this.promise.$$state))}catch(g){e[1](g),a(g)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;f(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;f<g;f++){e=d[f][0];b=d[f][3];try{e.notify(x(b)?b(c):c)}catch(h){a(h)}}})}});
|
||||
var l=function(a,b){var c=new h;b?c.resolve(a):c.reject(a);return c.promise},k=function(a,b,c){var d=null;try{x(c)&&(d=c())}catch(e){return l(e,!1)}return d&&x(d.then)?d.then(function(){return l(a,b)},function(a){return l(a,!1)}):l(a,b)},n=function(a,b,c,d){var e=new h;e.resolve(a);return e.promise.then(b,c,d)},p=function t(a){if(!x(a))throw g("norslvr",a);if(!(this instanceof t))return new t(a);var b=new h;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};p.defer=function(){return new h};
|
||||
p.reject=function(a){var b=new h;b.reject(a);return b.promise};p.when=n;p.resolve=n;p.all=function(a){var b=new h,c=0,d=J(a)?[]:{};m(a,function(a,e){c++;n(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};return p}function wf(){this.$get=["$window","$timeout",function(b,a){var c=b.requestAnimationFrame||b.webkitRequestAnimationFrame,d=b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.webkitCancelRequestAnimationFrame,
|
||||
e=!!c,f=e?function(a){var b=c(a);return function(){d(b)}}:function(b){var c=a(b,16.66,!1);return function(){a.cancel(c)}};f.supported=e;return f}]}function lf(){function b(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++nb;this.$$ChildScope=null}b.prototype=a;return b}var a=10,c=I("$rootScope"),d=null,e=null;this.digestTtl=function(b){arguments.length&&(a=b);return a};this.$get=
|
||||
["$injector","$exceptionHandler","$parse","$browser",function(f,h,g,l){function k(a){a.currentScope.$$destroyed=!0}function n(){this.$id=++nb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$$isolateBindings=null}function p(a){if(q.$$phase)throw c("inprog",q.$$phase);q.$$phase=a}function r(a,b){do a.$$watchersCount+=b;while(a=
|
||||
a.$parent)}function t(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function E(){}function s(){for(;w.length;)try{w.shift()()}catch(a){h(a)}e=null}function u(){null===e&&(e=l.defer(function(){q.$apply(s)}))}n.prototype={constructor:n,$new:function(a,c){var d;c=c||this;a?(d=new n,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=b(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=
|
||||
d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(a||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,c,e){var f=g(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,c,f,a);var h=this,k=h.$$watchers,l={fn:b,last:E,get:f,exp:e||a,eq:!!c};d=null;x(b)||(l.fn=y);k||(k=h.$$watchers=[]);k.unshift(l);r(this,1);return function(){0<=cb(k,l)&&r(h,-1);d=null}},$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;
|
||||
if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});m(a,function(a,b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!v(e)){if(C(e))if(Da(e))for(f!==p&&(f=p,t=f.length=0,l++),a=e.length,t!==a&&(l++,f.length=t=a),b=0;b<a;b++)h=f[b],
|
||||
g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==r&&(f=r={},t=0,l++);a=0;for(b in e)ta.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(t++,f[b]=g,l++));if(t>a)for(b in l++,f)ta.call(e,b)||(t--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1<b.length,l=0,n=g(a,c),p=[],r={},q=!0,t=0;return this.$watch(n,function(){q?(q=!1,b(e,e,d)):b(e,h,d);if(k)if(C(e))if(Da(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h=
|
||||
{},e)ta.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var b,f,g,k,n,r,t=a,m,u=[],D,v;p("$digest");l.$$checkUrlChange();this===q&&null!==e&&(l.defer.cancel(e),s());d=null;do{r=!1;for(m=this;z.length;){try{v=z.shift(),v.scope.$eval(v.expression,v.locals)}catch(w){h(w)}d=null}a:do{if(k=m.$$watchers)for(n=k.length;n--;)try{if(b=k[n])if((f=b.get(m))!==(g=b.last)&&!(b.eq?ka(f,g):"number"===typeof f&&"number"===typeof g&&isNaN(f)&&isNaN(g)))r=!0,d=b,b.last=b.eq?ha(f,null):f,b.fn(f,g===E?f:g,m),5>
|
||||
t&&(D=4-t,u[D]||(u[D]=[]),u[D].push({msg:x(b.exp)?"fn: "+(b.exp.name||b.exp.toString()):b.exp,newVal:f,oldVal:g}));else if(b===d){r=!1;break a}}catch(y){h(y)}if(!(k=m.$$watchersCount&&m.$$childHead||m!==this&&m.$$nextSibling))for(;m!==this&&!(k=m.$$nextSibling);)m=m.$parent}while(m=k);if((r||z.length)&&!t--)throw q.$$phase=null,c("infdig",a,u);}while(r||z.length);for(q.$$phase=null;N.length;)try{N.shift()()}catch(A){h(A)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");
|
||||
this.$$destroyed=!0;this===q&&l.$$applicationDestroyed();r(this,-this.$$watchersCount);for(var b in this.$$listenerCount)t(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=y;this.$on=
|
||||
this.$watch=this.$watchGroup=function(){return y};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){q.$$phase||z.length||l.defer(function(){z.length&&q.$digest()});z.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){N.push(a)},$apply:function(a){try{p("$apply");try{return this.$eval(a)}finally{q.$$phase=null}}catch(b){h(b)}finally{try{q.$digest()}catch(c){throw h(c),
|
||||
c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&w.push(b);u()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,t(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,f=!1,g={name:a,targetScope:e,stopPropagation:function(){f=!0},preventDefault:function(){g.defaultPrevented=!0},defaultPrevented:!1},
|
||||
k=db([g],arguments,1),l,n;do{d=e.$$listeners[a]||c;g.currentScope=e;l=0;for(n=d.length;l<n;l++)if(d[l])try{d[l].apply(null,k)}catch(p){h(p)}else d.splice(l,1),l--,n--;if(f)return g.currentScope=null,g;e=e.$parent}while(e);g.currentScope=null;return g},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var f=db([e],arguments,1),g,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||
|
||||
[];g=0;for(k=d.length;g<k;g++)if(d[g])try{d[g].apply(null,f)}catch(l){h(l)}else d.splice(g,1),g--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var q=new n,z=q.$$asyncQueue=[],N=q.$$postDigestQueue=[],w=q.$$applyAsyncQueue=[];return q}]}function ge(){var b=/^\s*(https?|ftp|mailto|tel|file):/,a=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(a){return A(a)?
|
||||
(b=a,this):b};this.imgSrcSanitizationWhitelist=function(b){return A(b)?(a=b,this):a};this.$get=function(){return function(c,d){var e=d?a:b,f;f=Aa(c).href;return""===f||f.match(e)?c:"unsafe:"+f}}}function Zf(b){if("self"===b)return b;if(G(b)){if(-1<b.indexOf("***"))throw Ca("iwcard",b);b=vd(b).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+b+"$")}if(Oa(b))return new RegExp("^"+b.source+"$");throw Ca("imatcher");}function wd(b){var a=[];A(b)&&m(b,function(b){a.push(Zf(b))});
|
||||
return a}function pf(){this.SCE_CONTEXTS=oa;var b=["self"],a=[];this.resourceUrlWhitelist=function(a){arguments.length&&(b=wd(a));return b};this.resourceUrlBlacklist=function(b){arguments.length&&(a=wd(b));return a};this.$get=["$injector",function(c){function d(a,b){return"self"===a?fd(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};
|
||||
return b}var f=function(a){throw Ca("unsafe");};c.has("$sanitize")&&(f=c.get("$sanitize"));var h=e(),g={};g[oa.HTML]=e(h);g[oa.CSS]=e(h);g[oa.URL]=e(h);g[oa.JS]=e(h);g[oa.RESOURCE_URL]=e(g[oa.URL]);return{trustAs:function(a,b){var c=g.hasOwnProperty(a)?g[a]:null;if(!c)throw Ca("icontext",a,b);if(null===b||v(b)||""===b)return b;if("string"!==typeof b)throw Ca("itype",a);return new c(b)},getTrusted:function(c,e){if(null===e||v(e)||""===e)return e;var h=g.hasOwnProperty(c)?g[c]:null;if(h&&e instanceof
|
||||
h)return e.$$unwrapTrustedValue();if(c===oa.RESOURCE_URL){var h=Aa(e.toString()),p,r,t=!1;p=0;for(r=b.length;p<r;p++)if(d(b[p],h)){t=!0;break}if(t)for(p=0,r=a.length;p<r;p++)if(d(a[p],h)){t=!1;break}if(t)return e;throw Ca("insecurl",e.toString());}if(c===oa.HTML)return f(e);throw Ca("unsafe");},valueOf:function(a){return a instanceof h?a.$$unwrapTrustedValue():a}}}]}function of(){var b=!0;this.enabled=function(a){arguments.length&&(b=!!a);return b};this.$get=["$parse","$sceDelegate",function(a,c){if(b&&
|
||||
8>Wa)throw Ca("iequirks");var d=ja(oa);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs=d.getTrusted=function(a,b){return b},d.valueOf=$a);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,h=d.trustAs;m(oa,function(a,b){var c=F(b);d[gb("parse_as_"+c)]=function(b){return e(a,b)};d[gb("get_trusted_"+c)]=function(b){return f(a,b)};d[gb("trust_as_"+
|
||||
c)]=function(b){return h(a,b)}});return d}]}function qf(){this.$get=["$window","$document",function(b,a){var c={},d=Y((/android (\d+)/.exec(F((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},h,g=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,n=!1;if(l){for(var p in l)if(k=g.exec(p)){h=k[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in
|
||||
l);!d||k&&n||(k=G(l.webkitTransition),n=G(l.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"===a&&11>=Wa)return!1;if(v(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:Fa(),vendorPrefix:h,transitions:k,animations:n,android:d}}]}function sf(){this.$get=["$templateCache","$http","$q","$sce",function(b,a,c,d){function e(f,h){e.totalPendingRequests++;G(f)&&b.get(f)||(f=d.getTrustedResourceUrl(f));var g=a.defaults&&a.defaults.transformResponse;
|
||||
J(g)?g=g.filter(function(a){return a!==Zb}):g===Zb&&(g=null);return a.get(f,{cache:b,transformResponse:g})["finally"](function(){e.totalPendingRequests--}).then(function(a){b.put(f,a.data);return a.data},function(a){if(!h)throw ga("tpload",f,a.status,a.statusText);return c.reject(a)})}e.totalPendingRequests=0;return e}]}function tf(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var h=[];m(a,function(a){var d=
|
||||
da.element(a).data("$binding");d&&m(d,function(d){c?(new RegExp("(^|\\s)"+vd(b)+"(\\s|\\||$)")).test(d)&&h.push(a):-1!=d.indexOf(b)&&h.push(a)})});return h},findModels:function(a,b,c){for(var h=["ng-","data-ng-","ng\\:"],g=0;g<h.length;++g){var l=a.querySelectorAll("["+h[g]+"model"+(c?"=":"*=")+'"'+b+'"]');if(l.length)return l}},getLocation:function(){return c.url()},setLocation:function(a){a!==c.url()&&(c.url(a),b.$digest())},whenStable:function(b){a.notifyWhenNoOutstandingRequests(b)}}}]}function uf(){this.$get=
|
||||
["$rootScope","$browser","$q","$$q","$exceptionHandler",function(b,a,c,d,e){function f(f,l,k){x(f)||(k=l,l=f,f=y);var n=ua.call(arguments,3),p=A(k)&&!k,r=(p?d:c).defer(),t=r.promise,m;m=a.defer(function(){try{r.resolve(f.apply(null,n))}catch(a){r.reject(a),e(a)}finally{delete h[t.$$timeoutId]}p||b.$apply()},l);t.$$timeoutId=m;h[m]=r;return t}var h={};f.cancel=function(b){return b&&b.$$timeoutId in h?(h[b.$$timeoutId].reject("canceled"),delete h[b.$$timeoutId],a.defer.cancel(b.$$timeoutId)):!1};return f}]}
|
||||
function Aa(b){Wa&&($.setAttribute("href",b),b=$.href);$.setAttribute("href",b);return{href:$.href,protocol:$.protocol?$.protocol.replace(/:$/,""):"",host:$.host,search:$.search?$.search.replace(/^\?/,""):"",hash:$.hash?$.hash.replace(/^#/,""):"",hostname:$.hostname,port:$.port,pathname:"/"===$.pathname.charAt(0)?$.pathname:"/"+$.pathname}}function fd(b){b=G(b)?Aa(b):b;return b.protocol===xd.protocol&&b.host===xd.host}function vf(){this.$get=qa(Q)}function yd(b){function a(a){try{return decodeURIComponent(a)}catch(b){return a}}
|
||||
var c=b[0]||{},d={},e="";return function(){var b,h,g,l,k;b=c.cookie||"";if(b!==e)for(e=b,b=e.split("; "),d={},g=0;g<b.length;g++)h=b[g],l=h.indexOf("="),0<l&&(k=a(h.substring(0,l)),v(d[k])&&(d[k]=a(h.substring(l+1))));return d}}function zf(){this.$get=yd}function Kc(b){function a(c,d){if(C(c)){var e={};m(c,function(b,c){e[c]=a(c,b)});return e}return b.factory(c+"Filter",d)}this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];a("currency",zd);a("date",Ad);
|
||||
a("filter",$f);a("json",ag);a("limitTo",bg);a("lowercase",cg);a("number",Bd);a("orderBy",Cd);a("uppercase",dg)}function $f(){return function(b,a,c){if(!Da(b)){if(null==b)return b;throw I("filter")("notarray",b);}var d;switch(gc(a)){case "function":break;case "boolean":case "null":case "number":case "string":d=!0;case "object":a=eg(a,c,d);break;default:return b}return Array.prototype.filter.call(b,a)}}function eg(b,a,c){var d=C(b)&&"$"in b;!0===a?a=ka:x(a)||(a=function(a,b){if(v(a))return!1;if(null===
|
||||
a||null===b)return a===b;if(C(b)||C(a)&&!qc(a))return!1;a=F(""+a);b=F(""+b);return-1!==a.indexOf(b)});return function(e){return d&&!C(e)?Ma(e,b.$,a,!1):Ma(e,b,a,c)}}function Ma(b,a,c,d,e){var f=gc(b),h=gc(a);if("string"===h&&"!"===a.charAt(0))return!Ma(b,a.substring(1),c,d);if(J(b))return b.some(function(b){return Ma(b,a,c,d)});switch(f){case "object":var g;if(d){for(g in b)if("$"!==g.charAt(0)&&Ma(b[g],a,c,!0))return!0;return e?!1:Ma(b,a,c,!1)}if("object"===h){for(g in a)if(e=a[g],!x(e)&&!v(e)&&
|
||||
(f="$"===g,!Ma(f?b:b[g],e,c,f,f)))return!1;return!0}return c(b,a);case "function":return!1;default:return c(b,a)}}function gc(b){return null===b?"null":typeof b}function zd(b){var a=b.NUMBER_FORMATS;return function(b,d,e){v(d)&&(d=a.CURRENCY_SYM);v(e)&&(e=a.PATTERNS[1].maxFrac);return null==b?b:Dd(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,e).replace(/\u00A4/g,d)}}function Bd(b){var a=b.NUMBER_FORMATS;return function(b,d){return null==b?b:Dd(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Dd(b,
|
||||
a,c,d,e){if(C(b))return"";var f=0>b;b=Math.abs(b);var h=Infinity===b;if(!h&&!isFinite(b))return"";var g=b+"",l="",k=!1,n=[];h&&(l="\u221e");if(!h&&-1!==g.indexOf("e")){var p=g.match(/([\d\.]+)e(-?)(\d+)/);p&&"-"==p[2]&&p[3]>e+1?b=0:(l=g,k=!0)}if(h||k)0<e&&1>b&&(l=b.toFixed(e),b=parseFloat(l),l=l.replace(hc,d));else{h=(g.split(hc)[1]||"").length;v(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var h=(""+b).split(hc),g=h[0],h=h[1]||"",p=0,
|
||||
r=a.lgSize,t=a.gSize;if(g.length>=r+t)for(p=g.length-r,k=0;k<p;k++)0===(p-k)%t&&0!==k&&(l+=c),l+=g.charAt(k);for(k=p;k<g.length;k++)0===(g.length-k)%r&&0!==k&&(l+=c),l+=g.charAt(k);for(;h.length<e;)h+="0";e&&"0"!==e&&(l+=d+h.substr(0,e))}0===b&&(f=!1);n.push(f?a.negPre:a.posPre,l,f?a.negSuf:a.posSuf);return n.join("")}function Gb(b,a,c){var d="";0>b&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function aa(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(0<
|
||||
c||e>-c)e+=c;0===e&&-12==c&&(e=12);return Gb(e,a,d)}}function Hb(b,a){return function(c,d){var e=c["get"+b](),f=sb(a?"SHORT"+b:b);return d[f][e]}}function Ed(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function Fd(b){return function(a){var c=Ed(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Gb(a,b)}}function ic(b,a){return 0>=b.getFullYear()?a.ERAS[0]:a.ERAS[1]}function Ad(b){function a(a){var b;if(b=
|
||||
a.match(c)){a=new Date(0);var f=0,h=0,g=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Y(b[9]+b[10]),h=Y(b[9]+b[11]));g.call(a,Y(b[1]),Y(b[2])-1,Y(b[3]));f=Y(b[4]||0)-f;h=Y(b[5]||0)-h;g=Y(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,h,g,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e,f){var h="",g=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;G(c)&&(c=
|
||||
fg.test(c)?Y(c):a(c));V(c)&&(c=new Date(c));if(!ea(c)||!isFinite(c.getTime()))return c;for(;e;)(k=gg.exec(e))?(g=db(g,k,1),e=g.pop()):(g.push(e),e=null);var n=c.getTimezoneOffset();f&&(n=wc(f,c.getTimezoneOffset()),c=Ob(c,f,!0));m(g,function(a){l=hg[a];h+=l?l(c,b.DATETIME_FORMATS,n):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return h}}function ag(){return function(b,a){v(a)&&(a=2);return eb(b,a)}}function bg(){return function(b,a,c){a=Infinity===Math.abs(Number(a))?Number(a):Y(a);if(isNaN(a))return b;
|
||||
V(b)&&(b=b.toString());if(!J(b)&&!G(b))return b;c=!c||isNaN(c)?0:Y(c);c=0>c&&c>=-b.length?b.length+c:c;return 0<=a?b.slice(c,c+a):0===c?b.slice(a,b.length):b.slice(Math.max(0,c+a),c)}}function Cd(b){function a(a,c){c=c?-1:1;return a.map(function(a){var d=1,g=$a;if(x(a))g=a;else if(G(a)){if("+"==a.charAt(0)||"-"==a.charAt(0))d="-"==a.charAt(0)?-1:1,a=a.substring(1);if(""!==a&&(g=b(a),g.constant))var l=g(),g=function(a){return a[l]}}return{get:g,descending:d*c}})}function c(a){switch(typeof a){case "number":case "boolean":case "string":return!0;
|
||||
default:return!1}}return function(b,e,f){if(!Da(b))return b;J(e)||(e=[e]);0===e.length&&(e=["+"]);var h=a(e,f);h.push({get:function(){return{}},descending:f?-1:1});b=Array.prototype.map.call(b,function(a,b){return{value:a,predicateValues:h.map(function(d){var e=d.get(a);d=typeof e;if(null===e)d="string",e="null";else if("string"===d)e=e.toLowerCase();else if("object"===d)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),c(e)))break a;if(qc(e)&&(e=e.toString(),c(e)))break a;e=b}return{value:e,type:d}})}});
|
||||
b.sort(function(a,b){for(var c=0,d=0,e=h.length;d<e;++d){var c=a.predicateValues[d],f=b.predicateValues[d],t=0;c.type===f.type?c.value!==f.value&&(t=c.value<f.value?-1:1):t=c.type<f.type?-1:1;if(c=t*h[d].descending)break}return c});return b=b.map(function(a){return a.value})}}function Na(b){x(b)&&(b={link:b});b.restrict=b.restrict||"AC";return qa(b)}function Gd(b,a,c,d,e){var f=this,h=[];f.$error={};f.$$success={};f.$pending=w;f.$name=e(a.name||a.ngForm||"")(c);f.$dirty=!1;f.$pristine=!0;f.$valid=
|
||||
!0;f.$invalid=!1;f.$submitted=!1;f.$$parentForm=Ib;f.$rollbackViewValue=function(){m(h,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){m(h,function(a){a.$commitViewValue()})};f.$addControl=function(a){Ta(a.$name,"input");h.push(a);a.$name&&(f[a.$name]=a);a.$$parentForm=f};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c];f[b]=a;a.$name=b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];m(f.$pending,function(b,c){f.$setValidity(c,null,a)});
|
||||
m(f.$error,function(b,c){f.$setValidity(c,null,a)});m(f.$$success,function(b,c){f.$setValidity(c,null,a)});cb(h,a);a.$$parentForm=Ib};Hd({ctrl:this,$element:b,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(cb(d,c),0===d.length&&delete a[b])},$animate:d});f.$setDirty=function(){d.removeClass(b,Ya);d.addClass(b,Jb);f.$dirty=!0;f.$pristine=!1;f.$$parentForm.$setDirty()};f.$setPristine=function(){d.setClass(b,Ya,Jb+" ng-submitted");f.$dirty=
|
||||
!1;f.$pristine=!0;f.$submitted=!1;m(h,function(a){a.$setPristine()})};f.$setUntouched=function(){m(h,function(a){a.$setUntouched()})};f.$setSubmitted=function(){d.addClass(b,"ng-submitted");f.$submitted=!0;f.$$parentForm.$setSubmitted()}}function jc(b){b.$formatters.push(function(a){return b.$isEmpty(a)?a:a.toString()})}function jb(b,a,c,d,e,f){var h=F(a[0].type);if(!e.android){var g=!1;a.on("compositionstart",function(a){g=!0});a.on("compositionend",function(){g=!1;l()})}var l=function(b){k&&(f.defer.cancel(k),
|
||||
k=null);if(!g){var e=a.val();b=b&&b.type;"password"===h||c.ngTrim&&"false"===c.ngTrim||(e=T(e));(d.$viewValue!==e||""===e&&d.$$hasNativeValidators)&&d.$setViewValue(e,b)}};if(e.hasEvent("input"))a.on("input",l);else{var k,n=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};a.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||n(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",n)}a.on("change",l);d.$render=function(){var b=d.$isEmpty(d.$viewValue)?
|
||||
"":d.$viewValue;a.val()!==b&&a.val(b)}}function Kb(b,a){return function(c,d){var e,f;if(ea(c))return c;if(G(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(ig.test(c))return new Date(c);b.lastIndex=0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},m(e,function(b,c){c<a.length&&(f[a[c]]=+b)}),new Date(f.yyyy,
|
||||
f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function kb(b,a,c,d){return function(e,f,h,g,l,k,n){function p(a){return a&&!(a.getTime&&a.getTime()!==a.getTime())}function r(a){return A(a)&&!ea(a)?c(a)||w:a}Id(e,f,h,g);jb(e,f,h,g,l,k);var t=g&&g.$options&&g.$options.timezone,m;g.$$parserName=b;g.$parsers.push(function(b){return g.$isEmpty(b)?null:a.test(b)?(b=c(b,m),t&&(b=Ob(b,t)),b):w});g.$formatters.push(function(a){if(a&&!ea(a))throw lb("datefmt",a);if(p(a))return(m=a)&&t&&(m=Ob(m,t,!0)),
|
||||
n("date")(a,d,t);m=null;return""});if(A(h.min)||h.ngMin){var s;g.$validators.min=function(a){return!p(a)||v(s)||c(a)>=s};h.$observe("min",function(a){s=r(a);g.$validate()})}if(A(h.max)||h.ngMax){var u;g.$validators.max=function(a){return!p(a)||v(u)||c(a)<=u};h.$observe("max",function(a){u=r(a);g.$validate()})}}}function Id(b,a,c,d){(d.$$hasNativeValidators=C(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?w:b})}function Jd(b,a,c,d,e){if(A(d)){b=
|
||||
b(d);if(!b.constant)throw lb("constexpr",c,d);return b(a)}return e}function kc(b,a){b="ngClass"+b;return["$animate",function(c){function d(a,b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],n=0;n<b.length;n++)if(e==b[n])continue a;c.push(e)}return c}function e(a){var b=[];return J(a)?(m(a,function(a){b=b.concat(e(a))}),b):G(a)?a.split(" "):C(a)?(m(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",link:function(f,h,g){function l(a,b){var c=h.data("$classCounts")||fa(),
|
||||
d=[];m(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});h.data("$classCounts",c);return d.join(" ")}function k(b){if(!0===a||f.$index%2===a){var k=e(b||[]);if(!n){var m=l(k,1);g.$addClass(m)}else if(!ka(b,n)){var s=e(n),m=d(k,s),k=d(s,k),m=l(m,1),k=l(k,-1);m&&m.length&&c.addClass(h,m);k&&k.length&&c.removeClass(h,k)}}n=ja(b)}var n;f.$watch(g[b],k,!0);g.$observe("class",function(a){k(f.$eval(g[b]))});"ngClass"!==b&&f.$watch("$index",function(c,d){var h=c&1;if(h!==(d&1)){var k=
|
||||
e(f.$eval(g[b]));h===a?(h=l(k,1),g.$addClass(h)):(h=l(k,-1),g.$removeClass(h))}})}}}]}function Hd(b){function a(a,b){b&&!f[a]?(l.addClass(e,a),f[a]=!0):!b&&f[a]&&(l.removeClass(e,a),f[a]=!1)}function c(b,c){b=b?"-"+Ac(b,"-"):"";a(mb+b,!0===c);a(Kd+b,!1===c)}var d=b.ctrl,e=b.$element,f={},h=b.set,g=b.unset,l=b.$animate;f[Kd]=!(f[mb]=e.hasClass(mb));d.$setValidity=function(b,e,f){v(e)?(d.$pending||(d.$pending={}),h(d.$pending,b,f)):(d.$pending&&g(d.$pending,b,f),Ld(d.$pending)&&(d.$pending=w));bb(e)?
|
||||
e?(g(d.$error,b,f),h(d.$$success,b,f)):(h(d.$error,b,f),g(d.$$success,b,f)):(g(d.$error,b,f),g(d.$$success,b,f));d.$pending?(a(Md,!0),d.$valid=d.$invalid=w,c("",null)):(a(Md,!1),d.$valid=Ld(d.$error),d.$invalid=!d.$valid,c("",d.$valid));e=d.$pending&&d.$pending[b]?w:d.$error[b]?!1:d.$$success[b]?!0:null;c(b,e);d.$$parentForm.$setValidity(b,e,d)}}function Ld(b){if(b)for(var a in b)if(b.hasOwnProperty(a))return!1;return!0}var jg=/^\/(.+)\/([a-z]*)$/,F=function(b){return G(b)?b.toLowerCase():b},ta=Object.prototype.hasOwnProperty,
|
||||
sb=function(b){return G(b)?b.toUpperCase():b},Wa,B,ra,ua=[].slice,Nf=[].splice,kg=[].push,va=Object.prototype.toString,rc=Object.getPrototypeOf,Ea=I("ng"),da=Q.angular||(Q.angular={}),Rb,nb=0;Wa=X.documentMode;y.$inject=[];$a.$inject=[];var J=Array.isArray,tc=/^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/,T=function(b){return G(b)?b.trim():b},vd=function(b){return b.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Fa=function(){if(!A(Fa.rules)){var b=
|
||||
X.querySelector("[ng-csp]")||X.querySelector("[data-ng-csp]");if(b){var a=b.getAttribute("ng-csp")||b.getAttribute("data-ng-csp");Fa.rules={noUnsafeEval:!a||-1!==a.indexOf("no-unsafe-eval"),noInlineStyle:!a||-1!==a.indexOf("no-inline-style")}}else{b=Fa;try{new Function(""),a=!1}catch(c){a=!0}b.rules={noUnsafeEval:a,noInlineStyle:!1}}}return Fa.rules},pb=function(){if(A(pb.name_))return pb.name_;var b,a,c=Qa.length,d,e;for(a=0;a<c;++a)if(d=Qa[a],b=X.querySelector("["+d.replace(":","\\:")+"jq]")){e=
|
||||
b.getAttribute(d+"jq");break}return pb.name_=e},Qa=["ng-","data-ng-","ng:","x-ng-"],be=/[A-Z]/g,Bc=!1,Qb,pa=1,Pa=3,fe={full:"1.4.7",major:1,minor:4,dot:7,codeName:"dark-luminescence"};R.expando="ng339";var hb=R.cache={},Ff=1;R._data=function(b){return this.cache[b[this.expando]]||{}};var Af=/([\:\-\_]+(.))/g,Bf=/^moz([A-Z])/,lg={mouseleave:"mouseout",mouseenter:"mouseover"},Tb=I("jqLite"),Ef=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Sb=/<|&#?\w+;/,Cf=/<([\w:-]+)/,Df=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
|
||||
ma={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,"",""]};ma.optgroup=ma.option;ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead;ma.th=ma.td;var Ra=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===X.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),R(Q).on("load",a))},
|
||||
toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<=b?B(this[b]):B(this[this.length+b])},length:0,push:kg,sort:[].sort,splice:[].splice},Bb={};m("multiple selected checked disabled readOnly required open".split(" "),function(b){Bb[F(b)]=b});var Sc={};m("input select option textarea button form details".split(" "),function(b){Sc[b]=!0});var $c={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};
|
||||
m({data:Vb,removeData:vb,hasData:function(b){for(var a in hb[b.ng339])return!0;return!1}},function(b,a){R[a]=b});m({data:Vb,inheritedData:Ab,scope:function(b){return B.data(b,"$scope")||Ab(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return B.data(b,"$isolateScope")||B.data(b,"$isolateScopeNoTemplate")},controller:Pc,injector:function(b){return Ab(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:xb,css:function(b,a,c){a=gb(a);if(A(c))b.style[a]=c;else return b.style[a]},
|
||||
attr:function(b,a,c){var d=b.nodeType;if(d!==Pa&&2!==d&&8!==d)if(d=F(a),Bb[d])if(A(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||y).specified?d:w;else if(A(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?w:b},prop:function(b,a,c){if(A(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(v(b)){var d=a.nodeType;return d===pa||d===Pa?a.textContent:""}a.textContent=b}b.$dv="";return b}(),
|
||||
val:function(b,a){if(v(a)){if(b.multiple&&"select"===wa(b)){var c=[];m(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(v(a))return b.innerHTML;ub(b,!0);b.innerHTML=a},empty:Qc},function(b,a){R.prototype[a]=function(a,d){var e,f,h=this.length;if(b!==Qc&&v(2==b.length&&b!==xb&&b!==Pc?a:d)){if(C(a)){for(e=0;e<h;e++)if(b===Vb)b(this[e],a);else for(f in a)b(this[e],f,a[f]);return this}e=b.$dv;h=v(e)?Math.min(h,1):h;
|
||||
for(f=0;f<h;f++){var g=b(this[f],a,d);e=e?e+g:g}return e}for(e=0;e<h;e++)b(this[e],a,d);return this}});m({removeData:vb,on:function a(c,d,e,f){if(A(f))throw Tb("onargs");if(Lc(c)){var h=wb(c,!0);f=h.events;var g=h.handle;g||(g=h.handle=Hf(c,f));for(var h=0<=d.indexOf(" ")?d.split(" "):[d],l=h.length;l--;){d=h[l];var k=f[d];k||(f[d]=[],"mouseenter"===d||"mouseleave"===d?a(c,lg[d],function(a){var c=a.relatedTarget;c&&(c===this||this.contains(c))||g(a,d)}):"$destroy"!==d&&c.addEventListener(d,g,!1),
|
||||
k=f[d]);k.push(e)}}},off:Oc,one:function(a,c,d){a=B(a);a.on(c,function f(){a.off(c,d);a.off(c,f)});a.on(c,d)},replaceWith:function(a,c){var d,e=a.parentNode;ub(a);m(new R(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];m(a.childNodes,function(a){a.nodeType===pa&&c.push(a)});return c},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,c){var d=a.nodeType;if(d===pa||11===d){c=new R(c);for(var d=0,e=c.length;d<
|
||||
e;d++)a.appendChild(c[d])}},prepend:function(a,c){if(a.nodeType===pa){var d=a.firstChild;m(new R(c),function(c){a.insertBefore(c,d)})}},wrap:function(a,c){c=B(c).eq(0).clone()[0];var d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:Wb,detach:function(a){Wb(a,!0)},after:function(a,c){var d=a,e=a.parentNode;c=new R(c);for(var f=0,h=c.length;f<h;f++){var g=c[f];e.insertBefore(g,d.nextSibling);d=g}},addClass:zb,removeClass:yb,toggleClass:function(a,c,d){c&&m(c.split(" "),function(c){var f=
|
||||
d;v(f)&&(f=!xb(a,c));(f?zb:yb)(a,c)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,c){return a.getElementsByTagName?a.getElementsByTagName(c):[]},clone:Ub,triggerHandler:function(a,c,d){var e,f,h=c.type||c,g=wb(a);if(g=(g=g&&g.events)&&g[h])e={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=
|
||||
!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:y,type:h,target:a},c.type&&(e=P(e,c)),c=ja(g),f=d?[e].concat(d):[e],m(c,function(c){e.isImmediatePropagationStopped()||c.apply(a,f)})}},function(a,c){R.prototype[c]=function(c,e,f){for(var h,g=0,l=this.length;g<l;g++)v(h)?(h=a(this[g],c,e,f),A(h)&&(h=B(h))):Nc(h,a(this[g],c,e,f));return A(h)?h:this};R.prototype.bind=R.prototype.on;R.prototype.unbind=R.prototype.off});Ua.prototype={put:function(a,
|
||||
c){this[Ga(a,this.nextUid)]=c},get:function(a){return this[Ga(a,this.nextUid)]},remove:function(a){var c=this[a=Ga(a,this.nextUid)];delete this[a];return c}};var yf=[function(){this.$get=[function(){return Ua}]}],Uc=/^[^\(]*\(\s*([^\)]*)\)/m,mg=/,/,ng=/^\s*(_?)(\S+?)\1\s*$/,Tc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ha=I("$injector");fb.$$annotate=function(a,c,d){var e;if("function"===typeof a){if(!(e=a.$inject)){e=[];if(a.length){if(c)throw G(d)&&d||(d=a.name||If(a)),Ha("strictdi",d);c=a.toString().replace(Tc,
|
||||
"");c=c.match(Uc);m(c[1].split(mg),function(a){a.replace(ng,function(a,c,d){e.push(d)})})}a.$inject=e}}else J(a)?(c=a.length-1,Sa(a[c],"fn"),e=a.slice(0,c)):Sa(a,"fn",!0);return e};var Nd=I("$animate"),Ue=function(){this.$get=["$q","$$rAF",function(a,c){function d(){}d.all=y;d.chain=y;d.prototype={end:y,cancel:y,resume:y,pause:y,complete:y,then:function(d,f){return a(function(a){c(function(){a()})}).then(d,f)}};return d}]},Te=function(){var a=new Ua,c=[];this.$get=["$$AnimateRunner","$rootScope",
|
||||
function(d,e){function f(a,c,d){var e=!1;c&&(c=G(c)?c.split(" "):J(c)?c:[],m(c,function(c){c&&(e=!0,a[c]=d)}));return e}function h(){m(c,function(c){var d=a.get(c);if(d){var e=Jf(c.attr("class")),f="",h="";m(d,function(a,c){a!==!!e[c]&&(a?f+=(f.length?" ":"")+c:h+=(h.length?" ":"")+c)});m(c,function(a){f&&zb(a,f);h&&yb(a,h)});a.remove(c)}});c.length=0}return{enabled:y,on:y,off:y,pin:y,push:function(g,l,k,n){n&&n();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(l=k.addClass,
|
||||
n=k.removeClass,k=a.get(g)||{},l=f(k,l,!0),n=f(k,n,!1),l||n)a.put(g,k),c.push(g),1===c.length&&e.$$postDigest(h);return new d}}}]},Re=["$provide",function(a){var c=this;this.$$registeredAnimations=Object.create(null);this.register=function(d,e){if(d&&"."!==d.charAt(0))throw Nd("notcsel",d);var f=d+"-animation";c.$$registeredAnimations[d.substr(1)]=f;a.factory(f,e)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Nd("nongcls",
|
||||
"ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function c(a,d,e){if(e){var l;a:{for(l=0;l<e.length;l++){var k=e[l];if(1===k.nodeType){l=k;break a}}l=void 0}!l||l.parentNode||l.previousElementSibling||(e=null)}e?e.after(a):d.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(f,h,g,l){h=h&&B(h);g=g&&B(g);h=h||g.parent();c(f,h,g);return a.push(f,"enter",Ia(l))},move:function(f,h,g,l){h=h&&B(h);g=g&&B(g);
|
||||
h=h||g.parent();c(f,h,g);return a.push(f,"move",Ia(l))},leave:function(c,e){return a.push(c,"leave",Ia(e),function(){c.remove()})},addClass:function(c,e,g){g=Ia(g);g.addClass=ib(g.addclass,e);return a.push(c,"addClass",g)},removeClass:function(c,e,g){g=Ia(g);g.removeClass=ib(g.removeClass,e);return a.push(c,"removeClass",g)},setClass:function(c,e,g,l){l=Ia(l);l.addClass=ib(l.addClass,e);l.removeClass=ib(l.removeClass,g);return a.push(c,"setClass",l)},animate:function(c,e,g,l,k){k=Ia(k);k.from=k.from?
|
||||
P(k.from,e):e;k.to=k.to?P(k.to,g):g;k.tempClasses=ib(k.tempClasses,l||"ng-inline-animate");return a.push(c,"animate",k)}}}]}],Se=function(){this.$get=["$$rAF","$q",function(a,c){var d=function(){};d.prototype={done:function(a){this.defer&&this.defer[!0===a?"reject":"resolve"]()},end:function(){this.done()},cancel:function(){this.done(!0)},getPromise:function(){this.defer||(this.defer=c.defer());return this.defer.promise},then:function(a,c){return this.getPromise().then(a,c)},"catch":function(a){return this.getPromise()["catch"](a)},
|
||||
"finally":function(a){return this.getPromise()["finally"](a)}};return function(c,f){function h(){a(function(){f.addClass&&(c.addClass(f.addClass),f.addClass=null);f.removeClass&&(c.removeClass(f.removeClass),f.removeClass=null);f.to&&(c.css(f.to),f.to=null);g||l.done();g=!0});return l}f.cleanupStyles&&(f.from=f.to=null);f.from&&(c.css(f.from),f.from=null);var g,l=new d;return{start:h,end:h}}}]},ga=I("$compile");Dc.$inject=["$provide","$$sanitizeUriProvider"];var Wc=/^((?:x|data)[\:\-_])/i,Of=I("$controller"),
|
||||
Vc=/^(\S+)(\s+as\s+(\w+))?$/,$e=function(){this.$get=["$document",function(a){return function(c){c?!c.nodeType&&c instanceof B&&(c=c[0]):c=a[0].body;return c.offsetWidth+1}}]},ad="application/json",$b={"Content-Type":ad+";charset=utf-8"},Qf=/^\[|^\{(?!\{)/,Rf={"[":/]$/,"{":/}$/},Pf=/^\)\]\}',?\n/,og=I("$http"),ed=function(a){return function(){throw og("legacy",a);}},La=da.$interpolateMinErr=I("$interpolate");La.throwNoconcat=function(a){throw La("noconcat",a);};La.interr=function(a,c){return La("interr",
|
||||
a,c.toString())};var pg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Tf={http:80,https:443,ftp:21},Db=I("$location"),qg={$$html5:!1,$$replace:!1,absUrl:Eb("$$absUrl"),url:function(a){if(v(a))return this.$$url;var c=pg.exec(a);(c[1]||""===a)&&this.path(decodeURIComponent(c[1]));(c[2]||c[1]||""===a)&&this.search(c[3]||"");this.hash(c[5]||"");return this},protocol:Eb("$$protocol"),host:Eb("$$host"),port:Eb("$$port"),path:jd("$$path",function(a){a=null!==a?a.toString():"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,
|
||||
c){switch(arguments.length){case 0:return this.$$search;case 1:if(G(a)||V(a))a=a.toString(),this.$$search=yc(a);else if(C(a))a=ha(a,{}),m(a,function(c,e){null==c&&delete a[e]}),this.$$search=a;else throw Db("isrcharg");break;default:v(c)||null===c?delete this.$$search[a]:this.$$search[a]=c}this.$$compose();return this},hash:jd("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};m([id,cc,bc],function(a){a.prototype=Object.create(qg);a.prototype.state=
|
||||
function(c){if(!arguments.length)return this.$$state;if(a!==bc||!this.$$html5)throw Db("nostate");this.$$state=v(c)?null:c;return this}});var Z=I("$parse"),Uf=Function.prototype.call,Vf=Function.prototype.apply,Wf=Function.prototype.bind,Lb=fa();m("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Lb[a]=!0});var rg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},ec=function(a){this.options=a};ec.prototype={constructor:ec,lex:function(a){this.text=a;this.index=0;for(this.tokens=
|
||||
[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdent(a))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var c=a+this.peek(),d=c+this.peek(2),e=Lb[c],f=Lb[d];Lb[a]||e||f?(a=f?d:e?c:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=
|
||||
a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,c){return-1!==c.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===
|
||||
a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=A(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c,d)+"]":" "+d;throw Z("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.index<this.text.length;){var d=F(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var e=this.peek();if("e"==d&&this.isExpOperator(e))a+=d;else if(this.isExpOperator(d)&&e&&this.isNumber(e)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||
|
||||
e&&this.isNumber(e)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:c,text:a,constant:!0,value:Number(a)})},readIdent:function(){for(var a=this.index;this.index<this.text.length;){var c=this.text.charAt(this.index);if(!this.isIdent(c)&&!this.isNumber(c))break;this.index++}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var c=this.index;this.index++;for(var d="",e=a,f=!1;this.index<this.text.length;){var h=
|
||||
this.text.charAt(this.index),e=e+h;if(f)"u"===h?(f=this.text.substring(this.index+1,this.index+5),f.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+f+"]"),this.index+=4,d+=String.fromCharCode(parseInt(f,16))):d+=rg[h]||h,f=!1;else if("\\"===h)f=!0;else{if(h===a){this.index++;this.tokens.push({index:c,text:e,constant:!0,value:d});return}d+=h}this.index++}this.throwError("Unterminated quote",c)}};var s=function(a,c){this.lexer=a;this.options=c};s.Program="Program";s.ExpressionStatement=
|
||||
"ExpressionStatement";s.AssignmentExpression="AssignmentExpression";s.ConditionalExpression="ConditionalExpression";s.LogicalExpression="LogicalExpression";s.BinaryExpression="BinaryExpression";s.UnaryExpression="UnaryExpression";s.CallExpression="CallExpression";s.MemberExpression="MemberExpression";s.Identifier="Identifier";s.Literal="Literal";s.ArrayExpression="ArrayExpression";s.Property="Property";s.ObjectExpression="ObjectExpression";s.ThisExpression="ThisExpression";s.NGValueParameter="NGValueParameter";
|
||||
s.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:s.Program,body:a}},expressionStatement:function(){return{type:s.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=
|
||||
this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:s.AssignmentExpression,left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),c,d;return this.expect("?")&&(c=this.expression(),this.consume(":"))?(d=this.expression(),{type:s.ConditionalExpression,test:a,alternate:c,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:s.LogicalExpression,
|
||||
operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:s.LogicalExpression,operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),c;c=this.expect("==","!=","===","!==");)a={type:s.BinaryExpression,operator:c.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),c;c=this.expect("<",">","<=",">=");)a={type:s.BinaryExpression,operator:c.text,
|
||||
left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a={type:s.BinaryExpression,operator:c.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:c.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},
|
||||
primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.constants.hasOwnProperty(this.peek().text)?a=ha(this.constants[this.consume().text]):this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var c;c=this.expect("(","[",".");)"("===c.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},
|
||||
this.consume(")")):"["===c.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===c.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var c={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return c},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.expression());
|
||||
while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],c;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;
|
||||
c={type:s.Property,kind:"init"};this.peek().constant?c.key=this.constant():this.peek().identifier?c.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");c.value=this.expression();a.push(c)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}},throwError:function(a,c){throw Z("syntax",c.text,a,c.index+1,this.text,this.text.substring(c.index));},consume:function(a){if(0===this.tokens.length)throw Z("ueoe",this.text);var c=this.expect(a);
|
||||
c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},peekToken:function(){if(0===this.tokens.length)throw Z("ueoe",this.text);return this.tokens[0]},peek:function(a,c,d,e){return this.peekAhead(0,a,c,d,e)},peekAhead:function(a,c,d,e,f){if(this.tokens.length>a){a=this.tokens[a];var h=a.text;if(h===c||h===d||h===e||h===f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},constants:{"true":{type:s.Literal,value:!0},
|
||||
"false":{type:s.Literal,value:!1},"null":{type:s.Literal,value:null},undefined:{type:s.Literal,value:w},"this":{type:s.ThisExpression}}};sd.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:c,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};U(e,d.$filter);var f="",h;this.stage="assign";if(h=qd(e))this.state.computing="assign",f=this.nextId(),this.recurse(h,f),this.return_(f),f="fn.assign="+this.generateFunction("assign",
|
||||
"s,v,l");h=od(e.body);d.stage="inputs";m(h,function(a,c){var e="fn"+c;d.state[e]={vars:[],body:[],own:{}};d.state.computing=e;var f=d.nextId();d.recurse(a,f);d.return_(f);d.state.inputs.push(e);a.watchId=c});this.state.computing="fn";this.stage="main";this.recurse(e);f='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+f+this.watchFns()+"return fn;";f=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue",
|
||||
"ensureSafeAssignContext","ifDefined","plus","text",f))(this.$filter,Xa,Ba,ld,kd,md,Xf,nd,a);this.state=this.stage=w;f.literal=rd(e);f.constant=e.constant;return f},USE:"use",STRICT:"strict",watchFns:function(){var a=[],c=this.state.inputs,d=this;m(c,function(c){a.push("var "+c+"="+d.generateFunction(c,"s"))});c.length&&a.push("fn.inputs=["+c.join(",")+"];");return a.join("")},generateFunction:function(a,c){return"function("+c+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=
|
||||
[],c=this;m(this.state.filters,function(d,e){a.push(d+"=$filter("+c.escape(e)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,c,d,e,f,h){var g,l,k=this,n,p;e=e||y;if(!h&&A(a.watchId))c=c||this.nextId(),this.if_("i",this.lazyAssign(c,this.computedMember("i",a.watchId)),this.lazyRecurse(a,c,d,e,f,!0));else switch(a.type){case s.Program:m(a.body,
|
||||
function(c,d){k.recurse(c.expression,w,w,function(a){l=a});d!==a.body.length-1?k.current().body.push(l,";"):k.return_(l)});break;case s.Literal:p=this.escape(a.value);this.assign(c,p);e(p);break;case s.UnaryExpression:this.recurse(a.argument,w,w,function(a){l=a});p=a.operator+"("+this.ifDefined(l,0)+")";this.assign(c,p);e(p);break;case s.BinaryExpression:this.recurse(a.left,w,w,function(a){g=a});this.recurse(a.right,w,w,function(a){l=a});p="+"===a.operator?this.plus(g,l):"-"===a.operator?this.ifDefined(g,
|
||||
0)+a.operator+this.ifDefined(l,0):"("+g+")"+a.operator+"("+l+")";this.assign(c,p);e(p);break;case s.LogicalExpression:c=c||this.nextId();k.recurse(a.left,c);k.if_("&&"===a.operator?c:k.not(c),k.lazyRecurse(a.right,c));e(c);break;case s.ConditionalExpression:c=c||this.nextId();k.recurse(a.test,c);k.if_(c,k.lazyRecurse(a.alternate,c),k.lazyRecurse(a.consequent,c));e(c);break;case s.Identifier:c=c||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",
|
||||
a.name)+"?l:s"),d.computed=!1,d.name=a.name);Xa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){f&&1!==f&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(c,k.nonComputedMember("s",a.name))})},c&&k.lazyAssign(c,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Fb(a.name))&&k.addEnsureSafeObject(c);e(c);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||
|
||||
this.nextId();c=c||this.nextId();k.recurse(a.object,g,w,function(){k.if_(k.notNull(g),function(){if(a.computed)l=k.nextId(),k.recurse(a.property,l),k.getStringValue(l),k.addEnsureSafeMemberName(l),f&&1!==f&&k.if_(k.not(k.computedMember(g,l)),k.lazyAssign(k.computedMember(g,l),"{}")),p=k.ensureSafeObject(k.computedMember(g,l)),k.assign(c,p),d&&(d.computed=!0,d.name=l);else{Xa(a.property.name);f&&1!==f&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),
|
||||
"{}"));p=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Fb(a.property.name))p=k.ensureSafeObject(p);k.assign(c,p);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(c,"undefined")});e(c)},!!f);break;case s.CallExpression:c=c||this.nextId();a.filter?(l=k.filter(a.callee.name),n=[],m(a.arguments,function(a){var c=k.nextId();k.recurse(a,c);n.push(c)}),p=l+"("+n.join(",")+")",k.assign(c,p),e(c)):(l=k.nextId(),g={},n=[],k.recurse(a.callee,l,g,function(){k.if_(k.notNull(l),
|
||||
function(){k.addEnsureSafeFunction(l);m(a.arguments,function(a){k.recurse(a,k.nextId(),w,function(a){n.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),p=k.member(g.context,g.name,g.computed)+"("+n.join(",")+")"):p=l+"("+n.join(",")+")";p=k.ensureSafeObject(p);k.assign(c,p)},function(){k.assign(c,"undefined")});e(c)}));break;case s.AssignmentExpression:l=this.nextId();g={};if(!pd(a.left))throw Z("lval");this.recurse(a.left,w,g,function(){k.if_(k.notNull(g.context),
|
||||
function(){k.recurse(a.right,l);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);p=k.member(g.context,g.name,g.computed)+a.operator+l;k.assign(c,p);e(c||p)})},1);break;case s.ArrayExpression:n=[];m(a.elements,function(a){k.recurse(a,k.nextId(),w,function(a){n.push(a)})});p="["+n.join(",")+"]";this.assign(c,p);e(p);break;case s.ObjectExpression:n=[];m(a.properties,function(a){k.recurse(a.value,k.nextId(),w,function(c){n.push(k.escape(a.key.type===
|
||||
s.Identifier?a.key.name:""+a.key.value)+":"+c)})});p="{"+n.join(",")+"}";this.assign(c,p);e(p);break;case s.ThisExpression:this.assign(c,"s");e("s");break;case s.NGValueParameter:this.assign(c,"v"),e("v")}},getHasOwnProperty:function(a,c){var d=a+"."+c,e=this.current().own;e.hasOwnProperty(d)||(e[d]=this.nextId(!1,a+"&&("+this.escape(c)+" in "+a+")"));return e[d]},assign:function(a,c){if(a)return this.current().body.push(a,"=",c,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=
|
||||
this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,c){return"ifDefined("+a+","+this.escape(c)+")"},plus:function(a,c){return"plus("+a+","+c+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,c,d){if(!0===a)c();else{var e=this.current().body;e.push("if(",a,"){");c();e.push("}");d&&(e.push("else{"),d(),e.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,c){return a+"."+c},computedMember:function(a,
|
||||
c){return a+"["+c+"]"},member:function(a,c,d){return d?this.computedMember(a,c):this.nonComputedMember(a,c)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+
|
||||
a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+",text)")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+a+",text)"},lazyRecurse:function(a,c,d,e,f,h){var g=this;return function(){g.recurse(a,c,d,e,f,h)}},lazyAssign:function(a,c){var d=this;return function(){d.assign(a,c)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,
|
||||
stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(G(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(V(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw Z("esc");},nextId:function(a,c){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(c?"="+c:""));return d},current:function(){return this.state[this.state.computing]}};
|
||||
td.prototype={compile:function(a,c){var d=this,e=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=c;U(e,d.$filter);var f,h;if(f=qd(e))h=this.recurse(f);f=od(e.body);var g;f&&(g=[],m(f,function(a,c){var e=d.recurse(a);a.input=e;g.push(e);a.watchId=c}));var l=[];m(e.body,function(a){l.push(d.recurse(a.expression))});f=0===e.body.length?function(){}:1===e.body.length?l[0]:function(a,c){var d;m(l,function(e){d=e(a,c)});return d};h&&(f.assign=function(a,c,d){return h(a,d,c)});g&&(f.inputs=
|
||||
g);f.literal=rd(e);f.constant=e.constant;return f},recurse:function(a,c,d){var e,f,h=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,c);case s.UnaryExpression:return f=this.recurse(a.argument),this["unary"+a.operator](f,c);case s.BinaryExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e,f,c);case s.LogicalExpression:return e=this.recurse(a.left),f=this.recurse(a.right),this["binary"+a.operator](e,
|
||||
f,c);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),c);case s.Identifier:return Xa(a.name,h.expression),h.identifier(a.name,h.expensiveChecks||Fb(a.name),c,d,h.expression);case s.MemberExpression:return e=this.recurse(a.object,!1,!!d),a.computed||(Xa(a.property.name,h.expression),f=a.property.name),a.computed&&(f=this.recurse(a.property)),a.computed?this.computedMember(e,f,c,d,h.expression):this.nonComputedMember(e,f,
|
||||
h.expensiveChecks,c,d,h.expression);case s.CallExpression:return g=[],m(a.arguments,function(a){g.push(h.recurse(a))}),a.filter&&(f=this.$filter(a.callee.name)),a.filter||(f=this.recurse(a.callee,!0)),a.filter?function(a,d,e,h){for(var r=[],m=0;m<g.length;++m)r.push(g[m](a,d,e,h));a=f.apply(w,r,h);return c?{context:w,name:w,value:a}:a}:function(a,d,e,p){var r=f(a,d,e,p),m;if(null!=r.value){Ba(r.context,h.expression);ld(r.value,h.expression);m=[];for(var s=0;s<g.length;++s)m.push(Ba(g[s](a,d,e,p),
|
||||
h.expression));m=Ba(r.value.apply(r.context,m),h.expression)}return c?{value:m}:m};case s.AssignmentExpression:return e=this.recurse(a.left,!0,1),f=this.recurse(a.right),function(a,d,g,p){var r=e(a,d,g,p);a=f(a,d,g,p);Ba(r.value,h.expression);md(r.context);r.context[r.name]=a;return c?{value:a}:a};case s.ArrayExpression:return g=[],m(a.elements,function(a){g.push(h.recurse(a))}),function(a,d,e,f){for(var h=[],m=0;m<g.length;++m)h.push(g[m](a,d,e,f));return c?{value:h}:h};case s.ObjectExpression:return g=
|
||||
[],m(a.properties,function(a){g.push({key:a.key.type===s.Identifier?a.key.name:""+a.key.value,value:h.recurse(a.value)})}),function(a,d,e,f){for(var h={},m=0;m<g.length;++m)h[g[m].key]=g[m].value(a,d,e,f);return c?{value:h}:h};case s.ThisExpression:return function(a){return c?{value:a}:a};case s.NGValueParameter:return function(a,d,e,f){return c?{value:e}:e}}},"unary+":function(a,c){return function(d,e,f,h){d=a(d,e,f,h);d=A(d)?+d:0;return c?{value:d}:d}},"unary-":function(a,c){return function(d,e,
|
||||
f,h){d=a(d,e,f,h);d=A(d)?-d:0;return c?{value:d}:d}},"unary!":function(a,c){return function(d,e,f,h){d=!a(d,e,f,h);return c?{value:d}:d}},"binary+":function(a,c,d){return function(e,f,h,g){var l=a(e,f,h,g);e=c(e,f,h,g);l=nd(l,e);return d?{value:l}:l}},"binary-":function(a,c,d){return function(e,f,h,g){var l=a(e,f,h,g);e=c(e,f,h,g);l=(A(l)?l:0)-(A(e)?e:0);return d?{value:l}:l}},"binary*":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)*c(e,f,h,g);return d?{value:e}:e}},"binary/":function(a,c,
|
||||
d){return function(e,f,h,g){e=a(e,f,h,g)/c(e,f,h,g);return d?{value:e}:e}},"binary%":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)%c(e,f,h,g);return d?{value:e}:e}},"binary===":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)===c(e,f,h,g);return d?{value:e}:e}},"binary!==":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)!==c(e,f,h,g);return d?{value:e}:e}},"binary==":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)==c(e,f,h,g);return d?{value:e}:e}},"binary!=":function(a,c,
|
||||
d){return function(e,f,h,g){e=a(e,f,h,g)!=c(e,f,h,g);return d?{value:e}:e}},"binary<":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)<c(e,f,h,g);return d?{value:e}:e}},"binary>":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)>c(e,f,h,g);return d?{value:e}:e}},"binary<=":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)<=c(e,f,h,g);return d?{value:e}:e}},"binary>=":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)>=c(e,f,h,g);return d?{value:e}:e}},"binary&&":function(a,c,d){return function(e,
|
||||
f,h,g){e=a(e,f,h,g)&&c(e,f,h,g);return d?{value:e}:e}},"binary||":function(a,c,d){return function(e,f,h,g){e=a(e,f,h,g)||c(e,f,h,g);return d?{value:e}:e}},"ternary?:":function(a,c,d,e){return function(f,h,g,l){f=a(f,h,g,l)?c(f,h,g,l):d(f,h,g,l);return e?{value:f}:f}},value:function(a,c){return function(){return c?{context:w,name:w,value:a}:a}},identifier:function(a,c,d,e,f){return function(h,g,l,k){h=g&&a in g?g:h;e&&1!==e&&h&&!h[a]&&(h[a]={});g=h?h[a]:w;c&&Ba(g,f);return d?{context:h,name:a,value:g}:
|
||||
g}},computedMember:function(a,c,d,e,f){return function(h,g,l,k){var n=a(h,g,l,k),p,m;null!=n&&(p=c(h,g,l,k),p=kd(p),Xa(p,f),e&&1!==e&&n&&!n[p]&&(n[p]={}),m=n[p],Ba(m,f));return d?{context:n,name:p,value:m}:m}},nonComputedMember:function(a,c,d,e,f,h){return function(g,l,k,n){g=a(g,l,k,n);f&&1!==f&&g&&!g[c]&&(g[c]={});l=null!=g?g[c]:w;(d||Fb(c))&&Ba(l,h);return e?{context:g,name:c,value:l}:l}},inputs:function(a,c){return function(d,e,f,h){return h?h[c]:a(d,e,f)}}};var fc=function(a,c,d){this.lexer=
|
||||
a;this.$filter=c;this.options=d;this.ast=new s(this.lexer);this.astCompiler=d.csp?new td(this.ast,c):new sd(this.ast,c)};fc.prototype={constructor:fc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};fa();fa();var Yf=Object.prototype.valueOf,Ca=I("$sce"),oa={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},ga=I("$compile"),$=X.createElement("a"),xd=Aa(Q.location.href);yd.$inject=["$document"];Kc.$inject=["$provide"];zd.$inject=["$locale"];Bd.$inject=
|
||||
["$locale"];var hc=".",hg={yyyy:aa("FullYear",4),yy:aa("FullYear",2,0,!0),y:aa("FullYear",1),MMMM:Hb("Month"),MMM:Hb("Month",!0),MM:aa("Month",2,1),M:aa("Month",1,1),dd:aa("Date",2),d:aa("Date",1),HH:aa("Hours",2),H:aa("Hours",1),hh:aa("Hours",2,-12),h:aa("Hours",1,-12),mm:aa("Minutes",2),m:aa("Minutes",1),ss:aa("Seconds",2),s:aa("Seconds",1),sss:aa("Milliseconds",3),EEEE:Hb("Day"),EEE:Hb("Day",!0),a:function(a,c){return 12>a.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a,c,d){a=-1*d;return a=(0<=
|
||||
a?"+":"")+(Gb(Math[0<a?"floor":"ceil"](a/60),2)+Gb(Math.abs(a%60),2))},ww:Fd(2),w:Fd(1),G:ic,GG:ic,GGG:ic,GGGG:function(a,c){return 0>=a.getFullYear()?c.ERANAMES[0]:c.ERANAMES[1]}},gg=/((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,fg=/^\-?\d+$/;Ad.$inject=["$locale"];var cg=qa(F),dg=qa(sb);Cd.$inject=["$parse"];var he=qa({restrict:"E",compile:function(a,c){if(!c.href&&!c.xlinkHref)return function(a,c){if("a"===c[0].nodeName.toLowerCase()){var f="[object SVGAnimatedString]"===
|
||||
va.call(c.prop("href"))?"xlink:href":"href";c.on("click",function(a){c.attr(f)||a.preventDefault()})}}}}),tb={};m(Bb,function(a,c){function d(a,d,f){a.$watch(f[e],function(a){f.$set(c,!!a)})}if("multiple"!=a){var e=ya("ng-"+c),f=d;"checked"===a&&(f=function(a,c,f){f.ngModel!==f[e]&&d(a,c,f)});tb[e]=function(){return{restrict:"A",priority:100,link:f}}}});m($c,function(a,c){tb[c]=function(){return{priority:100,link:function(a,e,f){if("ngPattern"===c&&"/"==f.ngPattern.charAt(0)&&(e=f.ngPattern.match(jg))){f.$set("ngPattern",
|
||||
new RegExp(e[1],e[2]));return}a.$watch(f[c],function(a){f.$set(c,a)})}}}});m(["src","srcset","href"],function(a){var c=ya("ng-"+a);tb[c]=function(){return{priority:99,link:function(d,e,f){var h=a,g=a;"href"===a&&"[object SVGAnimatedString]"===va.call(e.prop("href"))&&(g="xlinkHref",f.$attr[g]="xlink:href",h=null);f.$observe(c,function(c){c?(f.$set(g,c),Wa&&h&&e.prop(h,f[g])):"href"===a&&f.$set(g,null)})}}}});var Ib={$addControl:y,$$renameControl:function(a,c){a.$name=c},$removeControl:y,$setValidity:y,
|
||||
$setDirty:y,$setPristine:y,$setSubmitted:y};Gd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Od=function(a){return["$timeout","$parse",function(c,d){function e(a){return""===a?d('this[""]').assign:d(a).assign||y}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Gd,compile:function(d,h){d.addClass(Ya).addClass(mb);var g=h.name?"name":a&&h.ngForm?"ngForm":!1;return{pre:function(a,d,f,h){var m=h[0];if(!("action"in f)){var t=function(c){a.$apply(function(){m.$commitViewValue();
|
||||
m.$setSubmitted()});c.preventDefault()};d[0].addEventListener("submit",t,!1);d.on("$destroy",function(){c(function(){d[0].removeEventListener("submit",t,!1)},0,!1)})}(h[1]||m.$$parentForm).$addControl(m);var s=g?e(m.$name):y;g&&(s(a,m),f.$observe(g,function(c){m.$name!==c&&(s(a,w),m.$$parentForm.$$renameControl(m,c),s=e(m.$name),s(a,m))}));d.on("$destroy",function(){m.$$parentForm.$removeControl(m);s(a,w);P(m,Ib)})}}}}}]},ie=Od(),ve=Od(!0),ig=/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,
|
||||
sg=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,tg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i,ug=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Pd=/^(\d{4})-(\d{2})-(\d{2})$/,Qd=/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,lc=/^(\d{4})-W(\d\d)$/,Rd=/^(\d{4})-(\d\d)$/,Sd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Td={text:function(a,c,d,e,f,h){jb(a,c,d,e,f,h);jc(e)},date:kb("date",
|
||||
Pd,Kb(Pd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":kb("datetimelocal",Qd,Kb(Qd,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:kb("time",Sd,Kb(Sd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:kb("week",lc,function(a,c){if(ea(a))return a;if(G(a)){lc.lastIndex=0;var d=lc.exec(a);if(d){var e=+d[1],f=+d[2],h=d=0,g=0,l=0,k=Ed(e),f=7*(f-1);c&&(d=c.getHours(),h=c.getMinutes(),g=c.getSeconds(),l=c.getMilliseconds());return new Date(e,0,k.getDate()+f,d,h,g,l)}}return NaN},"yyyy-Www"),
|
||||
month:kb("month",Rd,Kb(Rd,["yyyy","MM"]),"yyyy-MM"),number:function(a,c,d,e,f,h){Id(a,c,d,e);jb(a,c,d,e,f,h);e.$$parserName="number";e.$parsers.push(function(a){return e.$isEmpty(a)?null:ug.test(a)?parseFloat(a):w});e.$formatters.push(function(a){if(!e.$isEmpty(a)){if(!V(a))throw lb("numfmt",a);a=a.toString()}return a});if(A(d.min)||d.ngMin){var g;e.$validators.min=function(a){return e.$isEmpty(a)||v(g)||a>=g};d.$observe("min",function(a){A(a)&&!V(a)&&(a=parseFloat(a,10));g=V(a)&&!isNaN(a)?a:w;e.$validate()})}if(A(d.max)||
|
||||
d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||v(l)||a<=l};d.$observe("max",function(a){A(a)&&!V(a)&&(a=parseFloat(a,10));l=V(a)&&!isNaN(a)?a:w;e.$validate()})}},url:function(a,c,d,e,f,h){jb(a,c,d,e,f,h);jc(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||sg.test(d)}},email:function(a,c,d,e,f,h){jb(a,c,d,e,f,h);jc(e);e.$$parserName="email";e.$validators.email=function(a,c){var d=a||c;return e.$isEmpty(d)||tg.test(d)}},radio:function(a,c,
|
||||
d,e){v(d.name)&&c.attr("name",++nb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,h,g,l){var k=Jd(l,a,"ngTrueValue",d.ngTrueValue,!0),n=Jd(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&&a.type)});e.$render=function(){c[0].checked=e.$viewValue};e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return ka(a,
|
||||
k)});e.$parsers.push(function(a){return a?k:n})},hidden:y,button:y,submit:y,reset:y,file:y},Ec=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,h,g,l){l[0]&&(Td[F(g.type)]||Td.text)(f,h,g,l[0],c,a,d,e)}}}}],vg=/^(true|false|\d+)$/,Ne=function(){return{restrict:"A",priority:100,compile:function(a,c){return vg.test(c.ngValue)?function(a,c,f){f.$set("value",a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",
|
||||
a)})}}}},ne=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=v(a)?"":a})}}}}],pe=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,h){d=a(f.attr(h.$attr.ngBindTemplate));c.$$addBindingInfo(f,d.expressions);f=f[0];h.$observe("ngBindTemplate",function(a){f.textContent=v(a)?"":a})}}}}],oe=["$sce","$parse",
|
||||
"$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var h=c(f.ngBindHtml),g=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(g,function(){e.html(a.getTrustedHtml(h(c))||"")})}}}}],Me=qa({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),qe=kc("",!0),se=kc("Odd",0),re=kc("Even",1),te=Na({compile:function(a,c){c.$set("ngCloak",
|
||||
w);a.removeClass("ng-cloak")}}),ue=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Jc={},wg={blur:!0,focus:!0};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ya("ng-"+a);Jc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,h){var g=d(h[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){g(c,{$event:d})};
|
||||
wg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var xe=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,h){var g,l,k;c.$watch(e.ngIf,function(c){c?l||h(function(c,f){l=f;c[c.length++]=X.createComment(" end ngIf: "+e.ngIf+" ");g={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),g&&(k=rb(g.clone),a.leave(k).then(function(){k=null}),g=null))})}}}],ye=["$templateRequest","$anchorScroll",
|
||||
"$animate",function(a,c,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:da.noop,compile:function(e,f){var h=f.ngInclude||f.src,g=f.onload||"",l=f.autoscroll;return function(e,f,m,r,t){var s=0,v,u,q,z=function(){u&&(u.remove(),u=null);v&&(v.$destroy(),v=null);q&&(d.leave(q).then(function(){u=null}),u=q,q=null)};e.$watch(h,function(h){var m=function(){!A(l)||l&&!e.$eval(l)||c()},p=++s;h?(a(h,!0).then(function(a){if(p===s){var c=e.$new();r.template=a;a=t(c,function(a){z();
|
||||
d.enter(a,null,f).then(m)});v=c;q=a;v.$emit("$includeContentLoaded",h);e.$eval(g)}},function(){p===s&&(z(),e.$emit("$includeContentError",h))}),e.$emit("$includeContentRequested",h)):(z(),r.template=null)})}}}}],Pe=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Mc(f.template,X).childNodes)(c,function(a){d.append(a)},{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],ze=Na({priority:450,
|
||||
compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Le=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",h="false"!==d.ngTrim,g=h?T(f):f;e.$parsers.push(function(a){if(!v(a)){var c=[];a&&m(a.split(g),function(a){a&&c.push(h?T(a):a)});return c}});e.$formatters.push(function(a){return J(a)?a.join(f):w});e.$isEmpty=function(a){return!a||!a.length}}}},mb="ng-valid",Kd="ng-invalid",Ya="ng-pristine",Jb="ng-dirty",Md=
|
||||
"ng-pending",lb=I("ngModel"),xg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,h,g,l,k,n){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=w;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=
|
||||
w;this.$name=n(d.name||"",!1)(a);this.$$parentForm=Ib;var p=f(d.ngModel),r=p.assign,t=p,s=r,K=null,u,q=this;this.$$setOptions=function(a){if((q.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");t=function(a){var d=p(a);x(d)&&(d=c(a));return d};s=function(a,c){x(p(a))?g(a,{$$$p:q.$modelValue}):r(a,q.$modelValue)}}else if(!p.assign)throw lb("nonassign",d.ngModel,xa(e));};this.$render=y;this.$isEmpty=function(a){return v(a)||""===a||null===a||a!==a};var z=0;Hd({ctrl:this,$element:e,
|
||||
set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},$animate:h});this.$setPristine=function(){q.$dirty=!1;q.$pristine=!0;h.removeClass(e,Jb);h.addClass(e,Ya)};this.$setDirty=function(){q.$dirty=!0;q.$pristine=!1;h.removeClass(e,Ya);h.addClass(e,Jb);q.$$parentForm.$setDirty()};this.$setUntouched=function(){q.$touched=!1;q.$untouched=!0;h.setClass(e,"ng-untouched","ng-touched")};this.$setTouched=function(){q.$touched=!0;q.$untouched=!1;h.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=
|
||||
function(){g.cancel(K);q.$viewValue=q.$$lastCommittedViewValue;q.$render()};this.$validate=function(){if(!V(q.$modelValue)||!isNaN(q.$modelValue)){var a=q.$$rawModelValue,c=q.$valid,d=q.$modelValue,e=q.$options&&q.$options.allowInvalid;q.$$runValidators(a,q.$$lastCommittedViewValue,function(f){e||c===f||(q.$modelValue=f?a:w,q.$modelValue!==d&&q.$$writeModelToScope())})}};this.$$runValidators=function(a,c,d){function e(){var d=!0;m(q.$validators,function(e,f){var h=e(a,c);d=d&&h;g(f,h)});return d?
|
||||
!0:(m(q.$asyncValidators,function(a,c){g(c,null)}),!1)}function f(){var d=[],e=!0;m(q.$asyncValidators,function(f,h){var k=f(a,c);if(!k||!x(k.then))throw lb("$asyncValidators",k);g(h,w);d.push(k.then(function(){g(h,!0)},function(a){e=!1;g(h,!1)}))});d.length?k.all(d).then(function(){h(e)},y):h(!0)}function g(a,c){l===z&&q.$setValidity(a,c)}function h(a){l===z&&d(a)}z++;var l=z;(function(){var a=q.$$parserName||"parse";if(v(u))g(a,null);else return u||(m(q.$validators,function(a,c){g(c,null)}),m(q.$asyncValidators,
|
||||
function(a,c){g(c,null)})),g(a,u),u;return!0})()?e()?f():h(!1):h(!1)};this.$commitViewValue=function(){var a=q.$viewValue;g.cancel(K);if(q.$$lastCommittedViewValue!==a||""===a&&q.$$hasNativeValidators)q.$$lastCommittedViewValue=a,q.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var c=q.$$lastCommittedViewValue;if(u=v(c)?w:!0)for(var d=0;d<q.$parsers.length;d++)if(c=q.$parsers[d](c),v(c)){u=!1;break}V(q.$modelValue)&&isNaN(q.$modelValue)&&(q.$modelValue=t(a));
|
||||
var e=q.$modelValue,f=q.$options&&q.$options.allowInvalid;q.$$rawModelValue=c;f&&(q.$modelValue=c,q.$modelValue!==e&&q.$$writeModelToScope());q.$$runValidators(c,q.$$lastCommittedViewValue,function(a){f||(q.$modelValue=a?c:w,q.$modelValue!==e&&q.$$writeModelToScope())})};this.$$writeModelToScope=function(){s(a,q.$modelValue);m(q.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};this.$setViewValue=function(a,c){q.$viewValue=a;q.$options&&!q.$options.updateOnDefault||q.$$debounceViewValueCommit(c)};
|
||||
this.$$debounceViewValueCommit=function(c){var d=0,e=q.$options;e&&A(e.debounce)&&(e=e.debounce,V(e)?d=e:V(e[c])?d=e[c]:V(e["default"])&&(d=e["default"]));g.cancel(K);d?K=g(function(){q.$commitViewValue()},d):l.$$phase?q.$commitViewValue():a.$apply(function(){q.$commitViewValue()})};a.$watch(function(){var c=t(a);if(c!==q.$modelValue&&(q.$modelValue===q.$modelValue||c===c)){q.$modelValue=q.$$rawModelValue=c;u=w;for(var d=q.$formatters,e=d.length,f=c;e--;)f=d[e](f);q.$viewValue!==f&&(q.$viewValue=
|
||||
q.$$lastCommittedViewValue=f,q.$render(),q.$$runValidators(c,f,y))}return c})}],Ke=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:xg,priority:1,compile:function(c){c.addClass(Ya).addClass("ng-untouched").addClass(mb);return{pre:function(a,c,f,h){var g=h[0];c=h[1]||g.$$parentForm;g.$$setOptions(h[2]&&h[2].$options);c.$addControl(g);f.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},
|
||||
post:function(c,e,f,h){var g=h[0];if(g.$options&&g.$options.updateOn)e.on(g.$options.updateOn,function(a){g.$$debounceViewValueCommit(a&&a.type)});e.on("blur",function(e){g.$touched||(a.$$phase?c.$evalAsync(g.$setTouched):c.$apply(g.$setTouched))})}}}}}],yg=/(\s+|^)default(\s+|$)/,Oe=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,c){var d=this;this.$options=ha(a.$eval(c.ngModelOptions));A(this.$options.updateOn)?(this.$options.updateOnDefault=!1,this.$options.updateOn=T(this.$options.updateOn.replace(yg,
|
||||
function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},Ae=Na({terminal:!0,priority:1E3}),zg=I("ngOptions"),Ag=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,Ie=["$compile","$parse",function(a,c){function d(a,d,e){function f(a,c,d,e,g){this.selectValue=a;this.viewValue=c;this.label=
|
||||
d;this.group=e;this.disabled=g}function n(a){var c;if(!s&&Da(a))c=a;else{c=[];for(var d in a)a.hasOwnProperty(d)&&"$"!==d.charAt(0)&&c.push(d)}return c}var m=a.match(Ag);if(!m)throw zg("iexp",a,xa(d));var r=m[5]||m[7],s=m[6];a=/ as /.test(m[0])&&m[1];var v=m[9];d=c(m[2]?m[1]:r);var w=a&&c(a)||d,u=v&&c(v),q=v?function(a,c){return u(e,c)}:function(a){return Ga(a)},z=function(a,c){return q(a,x(a,c))},y=c(m[2]||m[1]),A=c(m[3]||""),O=c(m[4]||""),H=c(m[8]),B={},x=s?function(a,c){B[s]=c;B[r]=a;return B}:
|
||||
function(a){B[r]=a;return B};return{trackBy:v,getTrackByValue:z,getWatchables:c(H,function(a){var c=[];a=a||[];for(var d=n(a),f=d.length,g=0;g<f;g++){var h=a===d?g:d[g],k=x(a[h],h),h=q(a[h],k);c.push(h);if(m[2]||m[1])h=y(e,k),c.push(h);m[4]&&(k=O(e,k),c.push(k))}return c}),getOptions:function(){for(var a=[],c={},d=H(e)||[],g=n(d),h=g.length,m=0;m<h;m++){var p=d===g?m:g[m],r=x(d[p],p),s=w(e,r),p=q(s,r),t=y(e,r),u=A(e,r),r=O(e,r),s=new f(p,s,t,u,r);a.push(s);c[p]=s}return{items:a,selectValueMap:c,getOptionFromViewValue:function(a){return c[z(a)]},
|
||||
getViewValueFromOption:function(a){return v?da.copy(a.viewValue):a.viewValue}}}}}var e=X.createElement("option"),f=X.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","?ngModel"],link:function(c,g,l,k){function n(a,c){a.element=c;c.disabled=a.disabled;a.label!==c.label&&(c.label=a.label,c.textContent=a.label);a.value!==c.value&&(c.value=a.selectValue)}function p(a,c,d,e){c&&F(c.nodeName)===d?d=c:(d=e.cloneNode(!1),c?a.insertBefore(d,c):a.appendChild(d));return d}function r(a){for(var c;a;)c=
|
||||
a.nextSibling,Wb(a),a=c}function s(a){var c=q&&q[0],d=H&&H[0];if(c||d)for(;a&&(a===c||a===d||c&&8===c.nodeType);)a=a.nextSibling;return a}function v(){var a=x&&u.readValue();x=C.getOptions();var c={},d=g[0].firstChild;O&&g.prepend(q);d=s(d);x.items.forEach(function(a){var h,k;a.group?(h=c[a.group],h||(h=p(g[0],d,"optgroup",f),d=h.nextSibling,h.label=a.group,h=c[a.group]={groupElement:h,currentOptionElement:h.firstChild}),k=p(h.groupElement,h.currentOptionElement,"option",e),n(a,k),h.currentOptionElement=
|
||||
k.nextSibling):(k=p(g[0],d,"option",e),n(a,k),d=k.nextSibling)});Object.keys(c).forEach(function(a){r(c[a].currentOptionElement)});r(d);w.$render();if(!w.$isEmpty(a)){var h=u.readValue();(C.trackBy?ka(a,h):a===h)||(w.$setViewValue(h),w.$render())}}var w=k[1];if(w){var u=k[0];k=l.multiple;for(var q,z=0,y=g.children(),A=y.length;z<A;z++)if(""===y[z].value){q=y.eq(z);break}var O=!!q,H=B(e.cloneNode(!1));H.val("?");var x,C=d(l.ngOptions,g,c);k?(w.$isEmpty=function(a){return!a||0===a.length},u.writeValue=
|
||||
function(a){x.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){(a=x.getOptionFromViewValue(a))&&!a.disabled&&(a.element.selected=!0)})},u.readValue=function(){var a=g.val()||[],c=[];m(a,function(a){(a=x.selectValueMap[a])&&!a.disabled&&c.push(x.getViewValueFromOption(a))});return c},C.trackBy&&c.$watchCollection(function(){if(J(w.$viewValue))return w.$viewValue.map(function(a){return C.getTrackByValue(a)})},function(){w.$render()})):(u.writeValue=function(a){var c=x.getOptionFromViewValue(a);
|
||||
c&&!c.disabled?g[0].value!==c.selectValue&&(H.remove(),O||q.remove(),g[0].value=c.selectValue,c.element.selected=!0,c.element.setAttribute("selected","selected")):null===a||O?(H.remove(),O||g.prepend(q),g.val(""),q.prop("selected",!0),q.attr("selected",!0)):(O||q.remove(),g.prepend(H),g.val("?"),H.prop("selected",!0),H.attr("selected",!0))},u.readValue=function(){var a=x.selectValueMap[g.val()];return a&&!a.disabled?(O||q.remove(),H.remove(),x.getViewValueFromOption(a)):null},C.trackBy&&c.$watch(function(){return C.getTrackByValue(w.$viewValue)},
|
||||
function(){w.$render()}));O?(q.remove(),a(q)(c),q.removeClass("ng-scope")):q=B(e.cloneNode(!1));v();c.$watchCollection(C.getWatchables,v)}}}}],Be=["$locale","$interpolate","$log",function(a,c,d){var e=/{}/g,f=/^when(Minus)?(.+)$/;return{link:function(h,g,l){function k(a){g.text(a||"")}var n=l.count,p=l.$attr.when&&g.attr(l.$attr.when),r=l.offset||0,s=h.$eval(p)||{},w={},A=c.startSymbol(),u=c.endSymbol(),q=A+n+"-"+r+u,z=da.noop,x;m(l,function(a,c){var d=f.exec(c);d&&(d=(d[1]?"-":"")+F(d[2]),s[d]=g.attr(l.$attr[c]))});
|
||||
m(s,function(a,d){w[d]=c(a.replace(e,q))});h.$watch(n,function(c){var e=parseFloat(c),f=isNaN(e);f||e in s||(e=a.pluralCat(e-r));e===x||f&&V(x)&&isNaN(x)||(z(),f=w[e],v(f)?(null!=c&&d.debug("ngPluralize: no rule defined for '"+e+"' in "+p),z=y,k()):z=h.$watch(f,k),x=e)})}}}],Ce=["$parse","$animate",function(a,c){var d=I("ngRepeat"),e=function(a,c,d,e,k,m,p){a[d]=e;k&&(a[k]=m);a.$index=c;a.$first=0===c;a.$last=c===p-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(c&1))};return{restrict:"A",
|
||||
multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,h){var g=h.ngRepeat,l=X.createComment(" end ngRepeat: "+g+" "),k=g.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!k)throw d("iexp",g);var n=k[1],p=k[2],r=k[3],s=k[4],k=n.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);if(!k)throw d("iidexp",n);var v=k[3]||k[1],y=k[2];if(r&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(r)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(r)))throw d("badident",
|
||||
r);var u,q,z,A,x={$id:Ga};s?u=a(s):(z=function(a,c){return Ga(c)},A=function(a){return a});return function(a,f,h,k,n){u&&(q=function(c,d,e){y&&(x[y]=c);x[v]=d;x.$index=e;return u(a,x)});var s=fa();a.$watchCollection(p,function(h){var k,p,t=f[0],u,x=fa(),C,G,J,M,I,F,L;r&&(a[r]=h);if(Da(h))I=h,p=q||z;else for(L in p=q||A,I=[],h)ta.call(h,L)&&"$"!==L.charAt(0)&&I.push(L);C=I.length;L=Array(C);for(k=0;k<C;k++)if(G=h===I?k:I[k],J=h[G],M=p(G,J,k),s[M])F=s[M],delete s[M],x[M]=F,L[k]=F;else{if(x[M])throw m(L,
|
||||
function(a){a&&a.scope&&(s[a.id]=a)}),d("dupes",g,M,J);L[k]={id:M,scope:w,clone:w};x[M]=!0}for(u in s){F=s[u];M=rb(F.clone);c.leave(M);if(M[0].parentNode)for(k=0,p=M.length;k<p;k++)M[k].$$NG_REMOVED=!0;F.scope.$destroy()}for(k=0;k<C;k++)if(G=h===I?k:I[k],J=h[G],F=L[k],F.scope){u=t;do u=u.nextSibling;while(u&&u.$$NG_REMOVED);F.clone[0]!=u&&c.move(rb(F.clone),null,B(t));t=F.clone[F.clone.length-1];e(F.scope,k,v,J,y,G,C)}else n(function(a,d){F.scope=d;var f=l.cloneNode(!1);a[a.length++]=f;c.enter(a,
|
||||
null,B(t));t=f;F.clone=a;x[F.id]=F;e(F.scope,k,v,J,y,G,C)});s=x})}}}}],De=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngShow,function(c){a[c?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],we=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(c,d,e){c.$watch(e.ngHide,function(c){a[c?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Ee=Na(function(a,c,d){a.$watch(d.ngStyle,
|
||||
function(a,d){d&&a!==d&&m(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Fe=["$animate",function(a){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,f){var h=[],g=[],l=[],k=[],n=function(a,c){return function(){a.splice(c,1)}};c.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=l.length;d<e;++d)a.cancel(l[d]);d=l.length=0;for(e=k.length;d<e;++d){var s=rb(g[d].clone);k[d].$destroy();(l[d]=a.leave(s)).then(n(l,d))}g.length=0;k.length=0;(h=f.cases["!"+
|
||||
c]||f.cases["?"])&&m(h,function(c){c.transclude(function(d,e){k.push(e);var f=c.element;d[d.length++]=X.createComment(" end ngSwitchWhen: ");g.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Ge=Na({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,c,d,e,f){e.cases["!"+d.ngSwitchWhen]=e.cases["!"+d.ngSwitchWhen]||[];e.cases["!"+d.ngSwitchWhen].push({transclude:f,element:c})}}),He=Na({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,
|
||||
c,d,e,f){e.cases["?"]=e.cases["?"]||[];e.cases["?"].push({transclude:f,element:c})}}),Je=Na({restrict:"EAC",link:function(a,c,d,e,f){if(!f)throw I("ngTransclude")("orphan",xa(c));f(function(a){c.empty();c.append(a)})}}),je=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){"text/ng-template"==d.type&&a.put(d.id,c[0].text)}}}],Bg={$setViewValue:y,$render:y},Cg=["$element","$scope","$attrs",function(a,c,d){var e=this,f=new Ua;e.ngModelCtrl=Bg;e.unknownOption=B(X.createElement("option"));
|
||||
e.renderUnknownOption=function(c){c="? "+Ga(c)+" ?";e.unknownOption.val(c);a.prepend(e.unknownOption);a.val(c)};c.$on("$destroy",function(){e.renderUnknownOption=y});e.removeUnknownOption=function(){e.unknownOption.parent()&&e.unknownOption.remove()};e.readValue=function(){e.removeUnknownOption();return a.val()};e.writeValue=function(c){e.hasOption(c)?(e.removeUnknownOption(),a.val(c),""===c&&e.emptyOption.prop("selected",!0)):null==c&&e.emptyOption?(e.removeUnknownOption(),a.val("")):e.renderUnknownOption(c)};
|
||||
e.addOption=function(a,c){Ta(a,'"option value"');""===a&&(e.emptyOption=c);var d=f.get(a)||0;f.put(a,d+1)};e.removeOption=function(a){var c=f.get(a);c&&(1===c?(f.remove(a),""===a&&(e.emptyOption=w)):f.put(a,c-1))};e.hasOption=function(a){return!!f.get(a)}}],ke=function(){return{restrict:"E",require:["select","?ngModel"],controller:Cg,link:function(a,c,d,e){var f=e[1];if(f){var h=e[0];h.ngModelCtrl=f;f.$render=function(){h.writeValue(f.$viewValue)};c.on("change",function(){a.$apply(function(){f.$setViewValue(h.readValue())})});
|
||||
if(d.multiple){h.readValue=function(){var a=[];m(c.find("option"),function(c){c.selected&&a.push(c.value)});return a};h.writeValue=function(a){var d=new Ua(a);m(c.find("option"),function(a){a.selected=A(d.get(a.value))})};var g,l=NaN;a.$watch(function(){l!==f.$viewValue||ka(g,f.$viewValue)||(g=ja(f.$viewValue),f.$render());l=f.$viewValue});f.$isEmpty=function(a){return!a||0===a.length}}}}}},me=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(c,d){if(A(d.value))var e=a(d.value,
|
||||
!0);else{var f=a(c.text(),!0);f||d.$set("value",c.text())}return function(a,c,d){function k(a){p.addOption(a,c);p.ngModelCtrl.$render();c[0].hasAttribute("selected")&&(c[0].selected=!0)}var m=c.parent(),p=m.data("$selectController")||m.parent().data("$selectController");if(p&&p.ngModelCtrl){if(e){var r;d.$observe("value",function(a){A(r)&&p.removeOption(r);r=a;k(a)})}else f?a.$watch(f,function(a,c){d.$set("value",a);c!==a&&p.removeOption(c);k(a)}):k(d.value);c.on("$destroy",function(){p.removeOption(d.value);
|
||||
p.ngModelCtrl.$render()})}}}}}],le=qa({restrict:"E",terminal:!1}),Gc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){e&&(d.required=!0,e.$validators.required=function(a,c){return!d.required||!e.$isEmpty(c)},d.$observe("required",function(){e.$validate()}))}}},Fc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f,h=d.ngPattern||d.pattern;d.$observe("pattern",function(a){G(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw I("ngPattern")("noregexp",
|
||||
h,a,xa(c));f=a||w;e.$validate()});e.$validators.pattern=function(a,c){return e.$isEmpty(c)||v(f)||f.test(c)}}}}},Ic=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=-1;d.$observe("maxlength",function(a){a=Y(a);f=isNaN(a)?-1:a;e.$validate()});e.$validators.maxlength=function(a,c){return 0>f||e.$isEmpty(c)||c.length<=f}}}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,c,d,e){if(e){var f=0;d.$observe("minlength",function(a){f=Y(a)||0;e.$validate()});
|
||||
e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};Q.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(ce(),ee(da),da.module("ngLocale",[],["$provide",function(a){function c(a){a+="";var c=a.indexOf(".");return-1==c?0:a.length-c-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),
|
||||
SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,
|
||||
maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",pluralCat:function(a,e){var f=a|0,h=e;w===h&&(h=Math.min(c(a),3));Math.pow(10,h);return 1==f&&0==h?"one":"other"}})}]),B(X).ready(function(){Zd(X,zc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
|
||||
//# sourceMappingURL=angular.min.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
5
static/bower_components/d3/d3.min.js
vendored
5
static/bower_components/d3/d3.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user