diff --git a/CHANGELOG.md b/CHANGELOG.md index f2038b3c0..b20c33ef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [v1.6.1](https://github.com/containous/traefik/tree/v1.6.1) (2018-05-14) +[All Commits](https://github.com/containous/traefik/compare/v1.6.0...v1.6.1) + +**Bug fixes:** +- **[acme]** Add missing deprecation info in CLI help. ([#3291](https://github.com/containous/traefik/pull/3291) by [ldez](https://github.com/ldez)) +- **[docker,marathon,rancher]** Fix segment backend name ([#3317](https://github.com/containous/traefik/pull/3317) by [ldez](https://github.com/ldez)) +- **[logs,middleware]** Error when accesslog and error pages ([#3314](https://github.com/containous/traefik/pull/3314) by [ldez](https://github.com/ldez)) +- **[middleware,tracing]** Fix wrong tag in forward span in tracing middleware ([#3279](https://github.com/containous/traefik/pull/3279) by [mmatur](https://github.com/mmatur)) +- **[webui]** Fix webui ([#3299](https://github.com/containous/traefik/pull/3299) by [ldez](https://github.com/ldez)) + +**Documentation:** +- **[k8s]** Add Documentation update for Kubernetes Ingress ([#3294](https://github.com/containous/traefik/pull/3294) by [dtomcej](https://github.com/dtomcej)) +- **[tls]** Enhance entry point TLS CLI reference. ([#3290](https://github.com/containous/traefik/pull/3290) by [ldez](https://github.com/ldez)) +- Typo in documentation ([#3261](https://github.com/containous/traefik/pull/3261) by [blakethepatton](https://github.com/blakethepatton)) + ## [v1.6.0](https://github.com/containous/traefik/tree/v1.6.0) (2018-04-30) [Commits](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0) [Commits pre RC](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0-rc1) diff --git a/README.md b/README.md index 00a875695..80a10da71 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy. Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically. -Telling Træfik where your orchestrator is could be the _only_ configuration step you need to do. +Pointing Træfik at your orchestrator should be the _only_ configuration step you need. --- diff --git a/acme/acme.go b/acme/acme.go index 805b2b744..f32445aeb 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -41,15 +41,15 @@ type ACME struct { Email string `description:"Email address used for registration"` Domains []types.Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"` Storage string `description:"File or key used for certificates storage."` - StorageFile string // deprecated - OnDemand bool `description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated + StorageFile string // Deprecated + OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated OnHostRule bool `description:"Enable certificate generation on frontends Host rules."` CAServer string `description:"CA server to use."` EntryPoint string `description:"Entrypoint to proxy acme challenge to."` DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"` HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"` - DNSProvider string `description:"Activate DNS-01 Challenge (Deprecated)"` // deprecated - DelayDontCheckDNS flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // deprecated + DNSProvider string `description:"(Deprecated) Activate DNS-01 Challenge"` // Deprecated + DelayDontCheckDNS flaeg.Duration `description:"(Deprecated) Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // Deprecated ACMELogging bool `description:"Enable debug logging of ACME actions."` client *acme.Client defaultCertificate *tls.Certificate diff --git a/configuration/configuration.go b/configuration/configuration.go index 3eded8d85..ded9cddc6 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -105,13 +105,13 @@ type GlobalConfiguration struct { // WebCompatibility is a configuration to handle compatibility with deprecated web provider options type WebCompatibility struct { - Address string `description:"Web administration port" export:"true"` - CertFile string `description:"SSL certificate" export:"true"` - KeyFile string `description:"SSL certificate" export:"true"` - ReadOnly bool `description:"Enable read only API" export:"true"` - Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"` - Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"` - Path string `description:"Root path for dashboard and API" export:"true"` + Address string `description:"(Deprecated) Web administration port" export:"true"` + CertFile string `description:"(Deprecated) SSL certificate" export:"true"` + KeyFile string `description:"(Deprecated) SSL certificate" export:"true"` + ReadOnly bool `description:"(Deprecated) Enable read only API" export:"true"` + Statistics *types.Statistics `description:"(Deprecated) Enable more detailed statistics" export:"true"` + Metrics *types.Metrics `description:"(Deprecated) Enable a metrics exporter" export:"true"` + Path string `description:"(Deprecated) Root path for dashboard and API" export:"true"` Auth *types.Auth `export:"true"` Debug bool `export:"true"` } diff --git a/configuration/entrypoints_test.go b/configuration/entrypoints_test.go index 26188e21d..231f82364 100644 --- a/configuration/entrypoints_test.go +++ b/configuration/entrypoints_test.go @@ -174,7 +174,7 @@ func TestEntryPoints_Set(t *testing.T) { name: "all parameters camelcase", expression: "Name:foo " + "Address::8000 " + - "TLS:goo,gii " + + "TLS:goo,gii;foo,fii " + "TLS " + "TLS.MinVersion:VersionTLS11 " + "TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " + @@ -211,6 +211,10 @@ func TestEntryPoints_Set(t *testing.T) { CertFile: tls.FileOrContent("goo"), KeyFile: tls.FileOrContent("gii"), }, + { + CertFile: tls.FileOrContent("foo"), + KeyFile: tls.FileOrContent("fii"), + }, }, ClientCA: tls.ClientCA{ Files: []string{"car"}, @@ -280,7 +284,7 @@ func TestEntryPoints_Set(t *testing.T) { name: "all parameters lowercase", expression: "Name:foo " + "address::8000 " + - "tls:goo,gii " + + "tls:goo,gii;foo,fii " + "tls " + "tls.minversion:VersionTLS11 " + "tls.ciphersuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA " + @@ -315,6 +319,10 @@ func TestEntryPoints_Set(t *testing.T) { CertFile: tls.FileOrContent("goo"), KeyFile: tls.FileOrContent("gii"), }, + { + CertFile: tls.FileOrContent("foo"), + KeyFile: tls.FileOrContent("fii"), + }, }, ClientCA: tls.ClientCA{ Files: []string{"car"}, diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 6200a75ac..bf0d6a7e8 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -288,12 +288,12 @@ Segment labels override the default behavior. | Label | Description | |---------------------------------------------------------------------------|-------------------------------------------------------------| +| `traefik..backend=BACKEND` | Same as `traefik.backend` | | `traefik..domain=DOMAIN` | Same as `traefik.domain` | | `traefik..port=PORT` | Same as `traefik.port` | | `traefik..protocol=http` | Same as `traefik.protocol` | | `traefik..weight=10` | Same as `traefik.weight` | | `traefik..frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` | -| `traefik..frontend.backend=BACKEND` | Same as `traefik.frontend.backend` | | `traefik..frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` | | `traefik..frontend.errors..backend=NAME` | Same as `traefik.frontend.errors..backend` | | `traefik..frontend.errors..query=PATH` | Same as `traefik.frontend.errors..query` | diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 21bbface6..53e26c230 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -112,7 +112,7 @@ Although traefik will connect directly to the endpoints (pods), it still checks If the service port defined in the ingress spec is 443, then the backend communication protocol is assumed to be TLS, and will connect via TLS automatically. !!! note - Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name. + Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name. If this is not an option, you may need to skip TLS certificate verification. See the [insecureSkipVerify](/configuration/commons/#main-section) setting for more details. @@ -137,7 +137,7 @@ The following general annotations are applicable on the Ingress object: | `traefik.ingress.kubernetes.io/redirect-replacement: http://mydomain/$1` | Redirect to another URL for that frontend. Must be set with `traefik.ingress.kubernetes.io/redirect-regex`. | | `traefik.ingress.kubernetes.io/rewrite-target: /users` | Replaces each matched Ingress path with the specified one, and adds the old path to the `X-Replaced-Path` header. | | `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` | Override the default frontend rule type. Default: `PathPrefix`. | -| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. | +| `traefik.ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"` | A comma-separated list of IP ranges permitted for access. all source IPs are permitted if the list is empty or a single range is ill-formatted. Please note, you may have to set `service.spec.externalTrafficPolicy` to the value `Local` to preserve the source IP of the request for filtering. Please see [this link](https://kubernetes.io/docs/tutorials/services/source-ip/) for more information.| | `traefik.ingress.kubernetes.io/app-root: "/index.html"` | Redirects all requests for `/` to the defined path. (4) | <1> `traefik.ingress.kubernetes.io/error-pages` example: @@ -262,4 +262,4 @@ More information are available in the [User Guide](/user-guide/kubernetes/#add- !!! note Only TLS certificates provided by users can be stored in Kubernetes Secrets. - [Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet. \ No newline at end of file + [Let's Encrypt](https://letsencrypt.org) certificates cannot be managed in Kubernets Secrets yet. diff --git a/docs/configuration/backends/marathon.md b/docs/configuration/backends/marathon.md index 8f35483a1..b7ca41b42 100644 --- a/docs/configuration/backends/marathon.md +++ b/docs/configuration/backends/marathon.md @@ -259,13 +259,13 @@ Segment labels override the default behavior. | Label | Description | |---------------------------------------------------------------------------|-------------------------------------------------------------| +| `traefik..backend=BACKEND` | Same as `traefik.backend` | | `traefik..domain=DOMAIN` | Same as `traefik.domain` | | `traefik..portIndex=1` | Same as `traefik.portIndex` | | `traefik..port=PORT` | Same as `traefik.port` | | `traefik..protocol=http` | Same as `traefik.protocol` | | `traefik..weight=10` | Same as `traefik.weight` | | `traefik..frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` | -| `traefik..frontend.backend=BACKEND` | Same as `traefik.frontend.backend` | | `traefik..frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` | | `traefik..frontend.errors..backend=NAME` | Same as `traefik.frontend.errors..backend` | | `traefik..frontend.errors..query=PATH` | Same as `traefik.frontend.errors..query` | diff --git a/docs/configuration/backends/rancher.md b/docs/configuration/backends/rancher.md index af4d42b59..8e3f8c94d 100644 --- a/docs/configuration/backends/rancher.md +++ b/docs/configuration/backends/rancher.md @@ -226,12 +226,12 @@ Segment labels override the default behavior. | Label | Description | |---------------------------------------------------------------------------|-------------------------------------------------------------| +| `traefik..backend=BACKEND` | Same as `traefik.backend` | | `traefik..domain=DOMAIN` | Same as `traefik.domain` | | `traefik..port=PORT` | Same as `traefik.port` | | `traefik..protocol=http` | Same as `traefik.protocol` | | `traefik..weight=10` | Same as `traefik.weight` | | `traefik..frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` | -| `traefik..frontend.backend=BACKEND` | Same as `traefik.frontend.backend` | | `traefik..frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` | | `traefik..frontend.errors..backend=NAME` | Same as `traefik.frontend.errors..backend` | | `traefik..frontend.errors..query=PATH` | Same as `traefik.frontend.errors..query` | diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index 2ffb1db9b..c874d85d3 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -106,7 +106,7 @@ traefik: ```ini Name:foo Address::80 -TLS:goo,gii +TLS:/my/path/foo.cert,/my/path/foo.key;/my/path/goo.cert,/my/path/goo.key;/my/path/hoo.cert,/my/path/hoo.key TLS TLS.MinVersion:VersionTLS11 TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384 diff --git a/docs/index.md b/docs/index.md index d9c866abd..5d07831e9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy. Træfik integrates with your existing infrastructure components ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), ...) and configures itself automatically and dynamically. -Telling Træfik where your orchestrator is could be the _only_ configuration step you need to do. +Pointing Træfik at your orchestrator should be the _only_ configuration step you need. ## Overview diff --git a/middlewares/accesslog/logger.go b/middlewares/accesslog/logger.go index 4754d7bba..f0480fc4d 100644 --- a/middlewares/accesslog/logger.go +++ b/middlewares/accesslog/logger.go @@ -101,19 +101,25 @@ func openAccessLogFile(filePath string) (*os.File, error) { return file, nil } -// GetLogDataTable gets the request context object that contains logging data. This accretes -// data as the request passes through the middleware chain. +// GetLogDataTable gets the request context object that contains logging data. +// This creates data as the request passes through the middleware chain. func GetLogDataTable(req *http.Request) *LogData { - return req.Context().Value(DataTableKey).(*LogData) + if ld, ok := req.Context().Value(DataTableKey).(*LogData); ok { + return ld + } + log.Errorf("%s is nil", DataTableKey) + return &LogData{Core: make(CoreLogData)} } func (l *LogHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) { now := time.Now().UTC() - core := make(CoreLogData) + + core := CoreLogData{ + StartUTC: now, + StartLocal: now.Local(), + } logDataTable := &LogData{Core: core, Request: req.Header} - core[StartUTC] = now - core[StartLocal] = now.Local() reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable)) diff --git a/middlewares/accesslog/save_backend.go b/middlewares/accesslog/save_backend.go index 353adbe71..674656a86 100644 --- a/middlewares/accesslog/save_backend.go +++ b/middlewares/accesslog/save_backend.go @@ -43,8 +43,6 @@ func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) { table.Core[OriginContentSize] = crw.Size() } -//------------------------------------------------------------------------------------------------- - // SaveFrontend sends the frontend name to the logger. These are sometimes used with a corresponding // SaveBackend handler, but not always. For example, redirected requests don't reach a backend. type SaveFrontend struct { diff --git a/middlewares/errorpages/error_pages.go b/middlewares/errorpages/error_pages.go index d73aa1eb0..dc460dbb6 100644 --- a/middlewares/errorpages/error_pages.go +++ b/middlewares/errorpages/error_pages.go @@ -99,7 +99,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http. utils.CopyHeaders(pageReq.Header, req.Header) utils.CopyHeaders(w.Header(), recorder.Header()) w.WriteHeader(recorder.GetCode()) - h.backendHandler.ServeHTTP(w, pageReq) + + h.backendHandler.ServeHTTP(w, pageReq.WithContext(req.Context())) return } } diff --git a/middlewares/tracing/forwarder.go b/middlewares/tracing/forwarder.go index 881302cb4..aa80486c5 100644 --- a/middlewares/tracing/forwarder.go +++ b/middlewares/tracing/forwarder.go @@ -33,7 +33,7 @@ func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, span.SetTag("frontend.name", f.frontend) span.SetTag("backend.name", f.backend) ext.HTTPMethod.Set(span, r.Method) - ext.HTTPUrl.Set(span, r.URL.String()) + ext.HTTPUrl.Set(span, fmt.Sprintf("%s%s", r.URL.String(), r.RequestURI)) span.SetTag("http.host", r.Host) InjectRequestHeaders(r) diff --git a/provider/docker/config.go b/provider/docker/config.go index 6e3421812..7ee1e2c76 100644 --- a/provider/docker/config.go +++ b/provider/docker/config.go @@ -262,7 +262,7 @@ func isBackendLBSwarm(container dockerData) bool { } func getSegmentBackendName(container dockerData) string { - if value := label.GetStringValue(container.SegmentLabels, label.TraefikFrontendBackend, ""); len(value) > 0 { + if value := label.GetStringValue(container.SegmentLabels, label.TraefikBackend, ""); len(value) > 0 { return provider.Normalize(container.ServiceName + "-" + value) } diff --git a/provider/docker/config_segment_test.go b/provider/docker/config_segment_test.go index f99060162..23350c4a6 100644 --- a/provider/docker/config_segment_test.go +++ b/provider/docker/config_segment_test.go @@ -253,7 +253,7 @@ func TestSegmentBuildConfiguration(t *testing.T) { "traefik.sauternes.port": "2503", "traefik.sauternes.protocol": "https", "traefik.sauternes.weight": "80", - "traefik.sauternes.frontend.backend": "foobar", + "traefik.sauternes.backend": "foobar", "traefik.sauternes.frontend.passHostHeader": "false", "traefik.sauternes.frontend.rule": "Path:/mypath", "traefik.sauternes.frontend.priority": "5000", diff --git a/provider/docker/deprecated_service.go b/provider/docker/deprecated_service.go index d6180d445..a709cf0d5 100644 --- a/provider/docker/deprecated_service.go +++ b/provider/docker/deprecated_service.go @@ -88,7 +88,7 @@ func extractServicePortV1(labelName string) []string { // Extract backend from labels for a given service and a given docker container // Deprecated func getServiceBackendNameV1(container dockerData, serviceName string) string { - if value, ok := getServiceLabelsV1(container, serviceName)[label.SuffixFrontendBackend]; ok { + if value, ok := getServiceLabelsV1(container, serviceName)[label.SuffixBackend]; ok { return provider.Normalize(container.ServiceName + "-" + value) } return provider.Normalize(container.ServiceName + "-" + getBackendNameV1(container) + "-" + serviceName) diff --git a/provider/docker/deprecated_service_test.go b/provider/docker/deprecated_service_test.go index 9d955b1dc..50c0a404c 100644 --- a/provider/docker/deprecated_service_test.go +++ b/provider/docker/deprecated_service_test.go @@ -162,7 +162,7 @@ func TestDockerServiceBuildConfigurationV1(t *testing.T) { "traefik.service.port": "2503", "traefik.service.protocol": "https", "traefik.service.weight": "80", - "traefik.service.frontend.backend": "foobar", + "traefik.service.backend": "foobar", "traefik.service.frontend.passHostHeader": "false", "traefik.service.frontend.rule": "Path:/mypath", "traefik.service.frontend.priority": "5000", @@ -595,7 +595,7 @@ func TestDockerGetServiceBackendNameV1(t *testing.T) { }, { container: containerJSON(labels(map[string]string{ - "traefik.myservice.frontend.backend": "custom-backend", + "traefik.myservice.backend": "custom-backend", })), expected: "fake-custom-backend", }, diff --git a/provider/label/label.go b/provider/label/label.go index 7c664e120..87bf950f8 100644 --- a/provider/label/label.go +++ b/provider/label/label.go @@ -59,6 +59,7 @@ func GetBoolValue(labels map[string]string, labelName string, defaultValue bool) if err == nil { return v } + log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err) } return defaultValue } diff --git a/provider/label/names.go b/provider/label/names.go index c6f0c318b..3efcc04f2 100644 --- a/provider/label/names.go +++ b/provider/label/names.go @@ -35,7 +35,6 @@ const ( SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression" SuffixFrontend = "frontend" SuffixFrontendAuthBasic = "frontend.auth.basic" - SuffixFrontendBackend = "frontend.backend" SuffixFrontendEntryPoints = "frontend.entryPoints" SuffixFrontendHeaders = "frontend.headers." SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders" @@ -105,7 +104,6 @@ const ( TraefikBackendBufferingRetryExpression = Prefix + SuffixBackendBufferingRetryExpression TraefikFrontend = Prefix + SuffixFrontend TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic - TraefikFrontendBackend = Prefix + SuffixFrontendBackend TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert diff --git a/provider/rancher/config.go b/provider/rancher/config.go index 6b59ca9fd..255850fe6 100644 --- a/provider/rancher/config.go +++ b/provider/rancher/config.go @@ -150,7 +150,7 @@ func getBackendName(service rancherData) string { } func getSegmentBackendName(service rancherData) string { - if value := label.GetStringValue(service.SegmentLabels, label.TraefikFrontendBackend, ""); len(value) > 0 { + if value := label.GetStringValue(service.SegmentLabels, label.TraefikBackend, ""); len(value) > 0 { return provider.Normalize(service.Name + "-" + value) } diff --git a/webui/.angular-cli.json b/webui/.angular-cli.json index 145265719..dc1419956 100644 --- a/webui/.angular-cli.json +++ b/webui/.angular-cli.json @@ -8,7 +8,7 @@ "root": "src", "outDir": "dist", "assets": [ - "assets", + "assets/images", "favicon.ico" ], "index": "index.html", @@ -19,7 +19,7 @@ "testTsconfig": "tsconfig.spec.json", "prefix": "app", "styles": [ - "styles/app.sass" + "app.sass" ], "scripts": [ "../node_modules/@fortawesome/fontawesome/index.js", diff --git a/webui/package.json b/webui/package.json index 9dec23f21..b52a59d60 100644 --- a/webui/package.json +++ b/webui/package.json @@ -27,7 +27,7 @@ "@angular/router": "^5.2.0", "@fortawesome/fontawesome": "^1.1.5", "@fortawesome/fontawesome-free-solid": "^5.0.10", - "bulma": "^0.6.2", + "bulma": "^0.7.0", "core-js": "^2.4.1", "d3": "^4.13.0", "date-fns": "^1.29.0", diff --git a/webui/src/app.sass b/webui/src/app.sass new file mode 100644 index 000000000..64a380502 --- /dev/null +++ b/webui/src/app.sass @@ -0,0 +1,27 @@ +@charset "utf-8" + +@import 'styles/typography' +@import 'styles/variables' +@import 'styles/colors' +@import '~bulma/sass/utilities/all' +@import '~bulma/sass/base/all' +@import '~bulma/sass/grid/all' +@import '~bulma/sass/elements/container' +@import '~bulma/sass/elements/tag' +@import '~bulma/sass/elements/other' +@import '~bulma/sass/elements/box' +@import '~bulma/sass/elements/form' +@import '~bulma/sass/elements/table' +@import '~bulma/sass/components/navbar' +@import '~bulma/sass/components/tabs' +@import '~bulma/sass/elements/notification' +@import 'styles/nav' +@import 'styles/content' +@import 'styles/message' +@import 'styles/charts' +@import 'styles/helper' + +html + font-family: $open-sans + height: 100% + background: $background diff --git a/webui/src/app/app.component.spec.ts b/webui/src/app/app.component.spec.ts index 16c7679ce..8836204f4 100644 --- a/webui/src/app/app.component.spec.ts +++ b/webui/src/app/app.component.spec.ts @@ -1,4 +1,4 @@ -import { TestBed, async } from '@angular/core/testing'; +import { async, TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { diff --git a/webui/src/app/app.module.ts b/webui/src/app/app.module.ts index a544f385f..c9ba2741a 100644 --- a/webui/src/app/app.module.ts +++ b/webui/src/app/app.module.ts @@ -1,18 +1,21 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; -import { RouterModule } from '@angular/router'; import { HttpClientModule } from '@angular/common/http'; +import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; +import { AppComponent } from './app.component'; +import { BarChartComponent } from './charts/bar-chart/bar-chart.component'; +import { LineChartComponent } from './charts/line-chart/line-chart.component'; +import { HeaderComponent } from './components/header/header.component'; +import { HealthComponent } from './components/health/health.component'; +import { ProvidersComponent } from './components/providers/providers.component'; +import { LetDirective } from './directives/let.directive'; +import { BackendFilterPipe } from './pipes/backend.filter.pipe'; +import { FrontendFilterPipe } from './pipes/frontend.filter.pipe'; +import { KeysPipe } from './pipes/keys.pipe'; import { ApiService } from './services/api.service'; import { WindowService } from './services/window.service'; -import { AppComponent } from './app.component'; -import { HeaderComponent } from './components/header/header.component'; -import { ProvidersComponent } from './components/providers/providers.component'; -import { HealthComponent } from './components/health/health.component'; -import { LineChartComponent } from './charts/line-chart/line-chart.component'; -import { BarChartComponent } from './charts/bar-chart/bar-chart.component'; -import { KeysPipe } from './pipes/keys.pipe'; @NgModule({ declarations: [ @@ -22,7 +25,10 @@ import { KeysPipe } from './pipes/keys.pipe'; HealthComponent, LineChartComponent, BarChartComponent, - KeysPipe + KeysPipe, + FrontendFilterPipe, + BackendFilterPipe, + LetDirective ], imports: [ BrowserModule, @@ -30,8 +36,8 @@ import { KeysPipe } from './pipes/keys.pipe'; HttpClientModule, FormsModule, RouterModule.forRoot([ - { path: '', component: ProvidersComponent, pathMatch: 'full' }, - { path: 'status', component: HealthComponent } + {path: '', component: ProvidersComponent, pathMatch: 'full'}, + {path: 'status', component: HealthComponent} ]) ], providers: [ diff --git a/webui/src/app/charts/bar-chart/bar-chart.component.ts b/webui/src/app/charts/bar-chart/bar-chart.component.ts index 8d130c4fb..47946d9ae 100644 --- a/webui/src/app/charts/bar-chart/bar-chart.component.ts +++ b/webui/src/app/charts/bar-chart/bar-chart.component.ts @@ -1,15 +1,7 @@ -import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { axisBottom, axisLeft, easeLinear, max, min, scaleBand, scaleLinear, select } from 'd3'; +import * as _ from 'lodash'; import { WindowService } from '../../services/window.service'; -import { - min, - max, - easeLinear, - select, - axisLeft, - axisBottom, - scaleBand, - scaleLinear -} from 'd3'; @Component({ selector: 'app-bar-chart', @@ -23,12 +15,12 @@ export class BarChartComponent implements OnInit, OnChanges { x: any; y: any; g: any; - bars: any; width: number; height: number; - margin = { top: 40, right: 40, bottom: 40, left: 40 }; + margin = {top: 40, right: 40, bottom: 40, left: 40}; loading: boolean; data: any[]; + previousData: any[]; constructor(public elementRef: ElementRef, public windowService: WindowService) { this.loading = true; @@ -37,7 +29,7 @@ export class BarChartComponent implements OnInit, OnChanges { ngOnInit() { this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart'); this.setup(); - setTimeout(() => this.loading = false, 4000); + setTimeout(() => this.loading = false, 1000); this.windowService.resize.subscribe(w => this.draw()); } @@ -47,15 +39,20 @@ export class BarChartComponent implements OnInit, OnChanges { return; } - this.data = this.value; - this.draw(); + if (!_.isEqual(this.previousData, this.value)) { + this.previousData = _.cloneDeep(this.value); + this.data = this.value; + + this.draw(); + } } setup(): void { this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right; this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom; - this.svg = select(this.barChartEl).append('svg') + this.svg = select(this.barChartEl) + .append('svg') .attr('width', this.width + this.margin.left + this.margin.right) .attr('height', this.height + this.margin.top + this.margin.bottom); @@ -73,11 +70,16 @@ export class BarChartComponent implements OnInit, OnChanges { } draw(): void { + if (this.barChartEl.clientWidth === 0 || this.barChartEl.clientHeight === 0) { + this.previousData = []; + } else { + this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right; + this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom; + } + this.x.domain(this.data.map((d: any) => d.code)); this.y.domain([0, max(this.data, (d: any) => d.count)]); - this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right; - this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom; this.svg .attr('width', this.width + this.margin.left + this.margin.right) @@ -93,17 +95,16 @@ export class BarChartComponent implements OnInit, OnChanges { this.g.select('.axis--y') .call(axisLeft(this.y).tickSize(-this.width)); + // Clean previous graph + this.g.selectAll('.bar').remove(); + const bars = this.g.selectAll('.bar').data(this.data); bars.enter() .append('rect') .attr('class', 'bar') - .attr('x', (d: any) => d.code) - .attr('y', (d: any) => d.count) - .attr('width', this.x.bandwidth()) - .attr('height', (d: any) => (this.height - this.y(d.count)) < 0 ? 0 : this.height - this.y(d.count)); - - bars.attr('x', (d: any) => this.x(d.code)) + .style('fill', (d: any) => 'hsl(' + Math.floor(((d.code - 100) * 310 / 427) + 50) + ', 50%, 50%)') + .attr('x', (d: any) => this.x(d.code)) .attr('y', (d: any) => this.y(d.count)) .attr('width', this.x.bandwidth()) .attr('height', (d: any) => (this.height - this.y(d.count)) < 0 ? 0 : this.height - this.y(d.count)); diff --git a/webui/src/app/charts/line-chart/line-chart.component.html b/webui/src/app/charts/line-chart/line-chart.component.html index 929411c3e..f216459d8 100644 --- a/webui/src/app/charts/line-chart/line-chart.component.html +++ b/webui/src/app/charts/line-chart/line-chart.component.html @@ -1,5 +1,5 @@
-
+
Loading, please wait... diff --git a/webui/src/app/charts/line-chart/line-chart.component.ts b/webui/src/app/charts/line-chart/line-chart.component.ts index 92fc7c0cd..e07f132ea 100644 --- a/webui/src/app/charts/line-chart/line-chart.component.ts +++ b/webui/src/app/charts/line-chart/line-chart.component.ts @@ -1,20 +1,20 @@ -import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core'; -import { WindowService } from '../../services/window.service'; +import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { - range, - scaleTime, - scaleLinear, - min, - max, - curveLinear, - line, - easeLinear, - select, - axisLeft, axisBottom, - timeSecond, - timeFormat + axisLeft, + curveLinear, + easeLinear, + line, + max, + min, + range, + scaleLinear, + scaleTime, + select, + timeFormat, + timeSecond } from 'd3'; +import { WindowService } from '../../services/window.service'; @Component({ selector: 'app-line-chart', @@ -23,7 +23,10 @@ import { export class LineChartComponent implements OnChanges, OnInit { @Input() value: { count: number, date: string }; + firstDisplay: boolean; + dirty: boolean; lineChartEl: HTMLElement; + loadingEl: HTMLElement; svg: any; g: any; line: any; @@ -39,15 +42,19 @@ export class LineChartComponent implements OnChanges, OnInit { yAxis: any; height: number; width: number; - margin = { top: 40, right: 40, bottom: 60, left: 60 }; + margin = {top: 40, right: 40, bottom: 60, left: 60}; loading = true; constructor(private elementRef: ElementRef, public windowService: WindowService) { } ngOnInit() { this.lineChartEl = this.elementRef.nativeElement.querySelector('.line-chart'); + this.loadingEl = this.elementRef.nativeElement.querySelector('.line-chart-loading'); this.limit = 40; + + // related to the Observable.timer(0, 3000) in health component this.duration = 3000; + this.now = new Date(Date.now() - this.duration); this.options = { @@ -55,22 +62,37 @@ export class LineChartComponent implements OnChanges, OnInit { color: '#3A84C5' }; + this.firstDisplay = true; this.render(); - setTimeout(() => this.loading = false, 4000); + this.windowService.resize.subscribe(w => { if (this.svg) { - const el = this.lineChartEl.querySelector('svg'); - el.parentNode.removeChild(el); + this.dirty = true; + this.loading = true; this.render(); } }); } render() { - this.width = this.lineChartEl.clientWidth - this.margin.left - this.margin.right; - this.height = this.lineChartEl.clientHeight - this.margin.top - this.margin.bottom; + // When the lineChartEl is not displayed (is-hidden), width and length are equal to 0. + let elt; + if (this.lineChartEl.clientWidth === 0 || this.lineChartEl.clientHeight === 0) { + elt = this.loadingEl; + } else { + elt = this.lineChartEl; + } + this.width = elt.clientWidth - this.margin.left - this.margin.right; + this.height = elt.clientHeight - this.margin.top - this.margin.bottom; - this.svg = select(this.lineChartEl).append('svg') + + const el = this.lineChartEl.querySelector('svg'); + if (el) { + el.parentNode.removeChild(el); + } + + this.svg = select(this.lineChartEl) + .append('svg') .attr('width', this.width + this.margin.left + this.margin.right) .attr('height', this.height + this.margin.top + this.margin.bottom) .append('g') @@ -80,7 +102,7 @@ export class LineChartComponent implements OnChanges, OnInit { this.data = range(this.limit).map(i => 0); } - this.x = scaleTime().range([0, this.width]); + this.x = scaleTime().range([0, this.width - 10]); this.y = scaleLinear().range([this.height, 0]); this.x.domain([this.now - (this.limit - 2), this.now - this.duration]); @@ -91,7 +113,9 @@ export class LineChartComponent implements OnChanges, OnInit { .y((d: any) => this.y(d)) .curve(curveLinear); - this.svg.append('defs').append('clipPath') + this.svg + .append('defs') + .append('clipPath') .attr('id', 'clip') .append('rect') .attr('width', this.width) @@ -121,7 +145,7 @@ export class LineChartComponent implements OnChanges, OnInit { this.updateData(this.value.count); } - updateData = (value: number) => { + updateData(value: number) { this.data.push(value * 1000000); this.now = new Date(); @@ -132,9 +156,13 @@ export class LineChartComponent implements OnChanges, OnInit { this.xAxis .transition() - .duration(this.duration) + .duration(this.firstDisplay || this.dirty ? 0 : this.duration) .ease(easeLinear) - .call(axisBottom(this.x).tickSize(-this.height).ticks(timeSecond, 5).tickFormat(timeFormat('%H:%M:%S'))) + .call(axisBottom(this.x).tickSize(-this.height).ticks(timeSecond, 5).tickFormat(timeFormat('%H:%M:%S'))); + + this.xAxis + .transition() + .duration(0) .selectAll('text') .style('text-anchor', 'end') .attr('dx', '-.8em') @@ -157,6 +185,13 @@ export class LineChartComponent implements OnChanges, OnInit { .ease(easeLinear) .attr('transform', `translate(${this.x(this.now - (this.limit - 1) * this.duration)})`); + this.firstDisplay = false; + this.dirty = false; + + if (this.loading) { + this.loading = false; + } + this.data.shift(); } } diff --git a/webui/src/app/components/header/header.component.html b/webui/src/app/components/header/header.component.html index 46fc6d6a3..7254a8afb 100644 --- a/webui/src/app/components/header/header.component.html +++ b/webui/src/app/components/header/header.component.html @@ -1,22 +1,27 @@ -