Merge branch 'v1.6' into master
This commit is contained in:
commit
4eda1e1bd4
50 changed files with 763 additions and 650 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,5 +1,20 @@
|
||||||
# Change Log
|
# 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)
|
## [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](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)
|
[Commits pre RC](https://github.com/containous/traefik/compare/v1.5.0-rc1...v1.6.0-rc1)
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
|
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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -41,15 +41,15 @@ type ACME struct {
|
||||||
Email string `description:"Email address used for registration"`
|
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'"`
|
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."`
|
Storage string `description:"File or key used for certificates storage."`
|
||||||
StorageFile string // deprecated
|
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
|
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."`
|
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||||
CAServer string `description:"CA server to use."`
|
CAServer string `description:"CA server to use."`
|
||||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||||
DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"`
|
DNSChallenge *acmeprovider.DNSChallenge `description:"Activate DNS-01 Challenge"`
|
||||||
HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"`
|
HTTPChallenge *acmeprovider.HTTPChallenge `description:"Activate HTTP-01 Challenge"`
|
||||||
DNSProvider string `description:"Activate DNS-01 Challenge (Deprecated)"` // deprecated
|
DNSProvider string `description:"(Deprecated) Activate DNS-01 Challenge"` // Deprecated
|
||||||
DelayDontCheckDNS flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."` // 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."`
|
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||||
client *acme.Client
|
client *acme.Client
|
||||||
defaultCertificate *tls.Certificate
|
defaultCertificate *tls.Certificate
|
||||||
|
|
|
@ -105,13 +105,13 @@ type GlobalConfiguration struct {
|
||||||
|
|
||||||
// WebCompatibility is a configuration to handle compatibility with deprecated web provider options
|
// WebCompatibility is a configuration to handle compatibility with deprecated web provider options
|
||||||
type WebCompatibility struct {
|
type WebCompatibility struct {
|
||||||
Address string `description:"Web administration port" export:"true"`
|
Address string `description:"(Deprecated) Web administration port" export:"true"`
|
||||||
CertFile string `description:"SSL certificate" export:"true"`
|
CertFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||||
KeyFile string `description:"SSL certificate" export:"true"`
|
KeyFile string `description:"(Deprecated) SSL certificate" export:"true"`
|
||||||
ReadOnly bool `description:"Enable read only API" export:"true"`
|
ReadOnly bool `description:"(Deprecated) Enable read only API" export:"true"`
|
||||||
Statistics *types.Statistics `description:"Enable more detailed statistics" export:"true"`
|
Statistics *types.Statistics `description:"(Deprecated) Enable more detailed statistics" export:"true"`
|
||||||
Metrics *types.Metrics `description:"Enable a metrics exporter" export:"true"`
|
Metrics *types.Metrics `description:"(Deprecated) Enable a metrics exporter" export:"true"`
|
||||||
Path string `description:"Root path for dashboard and API" export:"true"`
|
Path string `description:"(Deprecated) Root path for dashboard and API" export:"true"`
|
||||||
Auth *types.Auth `export:"true"`
|
Auth *types.Auth `export:"true"`
|
||||||
Debug bool `export:"true"`
|
Debug bool `export:"true"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
||||||
name: "all parameters camelcase",
|
name: "all parameters camelcase",
|
||||||
expression: "Name:foo " +
|
expression: "Name:foo " +
|
||||||
"Address::8000 " +
|
"Address::8000 " +
|
||||||
"TLS:goo,gii " +
|
"TLS:goo,gii;foo,fii " +
|
||||||
"TLS " +
|
"TLS " +
|
||||||
"TLS.MinVersion:VersionTLS11 " +
|
"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 " +
|
"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"),
|
CertFile: tls.FileOrContent("goo"),
|
||||||
KeyFile: tls.FileOrContent("gii"),
|
KeyFile: tls.FileOrContent("gii"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CertFile: tls.FileOrContent("foo"),
|
||||||
|
KeyFile: tls.FileOrContent("fii"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ClientCA: tls.ClientCA{
|
ClientCA: tls.ClientCA{
|
||||||
Files: []string{"car"},
|
Files: []string{"car"},
|
||||||
|
@ -280,7 +284,7 @@ func TestEntryPoints_Set(t *testing.T) {
|
||||||
name: "all parameters lowercase",
|
name: "all parameters lowercase",
|
||||||
expression: "Name:foo " +
|
expression: "Name:foo " +
|
||||||
"address::8000 " +
|
"address::8000 " +
|
||||||
"tls:goo,gii " +
|
"tls:goo,gii;foo,fii " +
|
||||||
"tls " +
|
"tls " +
|
||||||
"tls.minversion:VersionTLS11 " +
|
"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 " +
|
"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"),
|
CertFile: tls.FileOrContent("goo"),
|
||||||
KeyFile: tls.FileOrContent("gii"),
|
KeyFile: tls.FileOrContent("gii"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CertFile: tls.FileOrContent("foo"),
|
||||||
|
KeyFile: tls.FileOrContent("fii"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ClientCA: tls.ClientCA{
|
ClientCA: tls.ClientCA{
|
||||||
Files: []string{"car"},
|
Files: []string{"car"},
|
||||||
|
|
|
@ -288,12 +288,12 @@ Segment labels override the default behavior.
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description |
|
||||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||||
|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||||
| `traefik.<segment_name>.frontend.backend=BACKEND` | Same as `traefik.frontend.backend` |
|
|
||||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||||
|
|
|
@ -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/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/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/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) |
|
| `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:
|
<1> `traefik.ingress.kubernetes.io/error-pages` example:
|
||||||
|
|
|
@ -259,13 +259,13 @@ Segment labels override the default behavior.
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description |
|
||||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||||
|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||||
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
| `traefik.<segment_name>.portIndex=1` | Same as `traefik.portIndex` |
|
||||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||||
| `traefik.<segment_name>.frontend.backend=BACKEND` | Same as `traefik.frontend.backend` |
|
|
||||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||||
|
|
|
@ -226,12 +226,12 @@ Segment labels override the default behavior.
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description |
|
||||||
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
|---------------------------------------------------------------------------|-------------------------------------------------------------|
|
||||||
|
| `traefik.<segment_name>.backend=BACKEND` | Same as `traefik.backend` |
|
||||||
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
| `traefik.<segment_name>.domain=DOMAIN` | Same as `traefik.domain` |
|
||||||
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
| `traefik.<segment_name>.port=PORT` | Same as `traefik.port` |
|
||||||
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
| `traefik.<segment_name>.protocol=http` | Same as `traefik.protocol` |
|
||||||
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
| `traefik.<segment_name>.weight=10` | Same as `traefik.weight` |
|
||||||
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
| `traefik.<segment_name>.frontend.auth.basic=EXPR` | Same as `traefik.frontend.auth.basic` |
|
||||||
| `traefik.<segment_name>.frontend.backend=BACKEND` | Same as `traefik.frontend.backend` |
|
|
||||||
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
| `traefik.<segment_name>.frontend.entryPoints=https` | Same as `traefik.frontend.entryPoints` |
|
||||||
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
| `traefik.<segment_name>.frontend.errors.<name>.backend=NAME` | Same as `traefik.frontend.errors.<name>.backend` |
|
||||||
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
| `traefik.<segment_name>.frontend.errors.<name>.query=PATH` | Same as `traefik.frontend.errors.<name>.query` |
|
||||||
|
|
|
@ -106,7 +106,7 @@ traefik:
|
||||||
```ini
|
```ini
|
||||||
Name:foo
|
Name:foo
|
||||||
Address::80
|
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
|
||||||
TLS.MinVersion:VersionTLS11
|
TLS.MinVersion:VersionTLS11
|
||||||
TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384
|
TLS.CipherSuites:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
Træfik is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
|
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.
|
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
|
## Overview
|
||||||
|
|
||||||
|
|
|
@ -101,19 +101,25 @@ func openAccessLogFile(filePath string) (*os.File, error) {
|
||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogDataTable gets the request context object that contains logging data. This accretes
|
// GetLogDataTable gets the request context object that contains logging data.
|
||||||
// data as the request passes through the middleware chain.
|
// This creates data as the request passes through the middleware chain.
|
||||||
func GetLogDataTable(req *http.Request) *LogData {
|
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) {
|
func (l *LogHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.HandlerFunc) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
core := make(CoreLogData)
|
|
||||||
|
core := CoreLogData{
|
||||||
|
StartUTC: now,
|
||||||
|
StartLocal: now.Local(),
|
||||||
|
}
|
||||||
|
|
||||||
logDataTable := &LogData{Core: core, Request: req.Header}
|
logDataTable := &LogData{Core: core, Request: req.Header}
|
||||||
core[StartUTC] = now
|
|
||||||
core[StartLocal] = now.Local()
|
|
||||||
|
|
||||||
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,6 @@ func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
table.Core[OriginContentSize] = crw.Size()
|
table.Core[OriginContentSize] = crw.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// SaveFrontend sends the frontend name to the logger. These are sometimes used with a corresponding
|
// 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.
|
// SaveBackend handler, but not always. For example, redirected requests don't reach a backend.
|
||||||
type SaveFrontend struct {
|
type SaveFrontend struct {
|
||||||
|
|
|
@ -99,7 +99,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
||||||
utils.CopyHeaders(pageReq.Header, req.Header)
|
utils.CopyHeaders(pageReq.Header, req.Header)
|
||||||
utils.CopyHeaders(w.Header(), recorder.Header())
|
utils.CopyHeaders(w.Header(), recorder.Header())
|
||||||
w.WriteHeader(recorder.GetCode())
|
w.WriteHeader(recorder.GetCode())
|
||||||
h.backendHandler.ServeHTTP(w, pageReq)
|
|
||||||
|
h.backendHandler.ServeHTTP(w, pageReq.WithContext(req.Context()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request,
|
||||||
span.SetTag("frontend.name", f.frontend)
|
span.SetTag("frontend.name", f.frontend)
|
||||||
span.SetTag("backend.name", f.backend)
|
span.SetTag("backend.name", f.backend)
|
||||||
ext.HTTPMethod.Set(span, r.Method)
|
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)
|
span.SetTag("http.host", r.Host)
|
||||||
|
|
||||||
InjectRequestHeaders(r)
|
InjectRequestHeaders(r)
|
||||||
|
|
|
@ -262,7 +262,7 @@ func isBackendLBSwarm(container dockerData) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSegmentBackendName(container dockerData) string {
|
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)
|
return provider.Normalize(container.ServiceName + "-" + value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -253,7 +253,7 @@ func TestSegmentBuildConfiguration(t *testing.T) {
|
||||||
"traefik.sauternes.port": "2503",
|
"traefik.sauternes.port": "2503",
|
||||||
"traefik.sauternes.protocol": "https",
|
"traefik.sauternes.protocol": "https",
|
||||||
"traefik.sauternes.weight": "80",
|
"traefik.sauternes.weight": "80",
|
||||||
"traefik.sauternes.frontend.backend": "foobar",
|
"traefik.sauternes.backend": "foobar",
|
||||||
"traefik.sauternes.frontend.passHostHeader": "false",
|
"traefik.sauternes.frontend.passHostHeader": "false",
|
||||||
"traefik.sauternes.frontend.rule": "Path:/mypath",
|
"traefik.sauternes.frontend.rule": "Path:/mypath",
|
||||||
"traefik.sauternes.frontend.priority": "5000",
|
"traefik.sauternes.frontend.priority": "5000",
|
||||||
|
|
|
@ -88,7 +88,7 @@ func extractServicePortV1(labelName string) []string {
|
||||||
// Extract backend from labels for a given service and a given docker container
|
// Extract backend from labels for a given service and a given docker container
|
||||||
// Deprecated
|
// Deprecated
|
||||||
func getServiceBackendNameV1(container dockerData, serviceName string) string {
|
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 + "-" + value)
|
||||||
}
|
}
|
||||||
return provider.Normalize(container.ServiceName + "-" + getBackendNameV1(container) + "-" + serviceName)
|
return provider.Normalize(container.ServiceName + "-" + getBackendNameV1(container) + "-" + serviceName)
|
||||||
|
|
|
@ -162,7 +162,7 @@ func TestDockerServiceBuildConfigurationV1(t *testing.T) {
|
||||||
"traefik.service.port": "2503",
|
"traefik.service.port": "2503",
|
||||||
"traefik.service.protocol": "https",
|
"traefik.service.protocol": "https",
|
||||||
"traefik.service.weight": "80",
|
"traefik.service.weight": "80",
|
||||||
"traefik.service.frontend.backend": "foobar",
|
"traefik.service.backend": "foobar",
|
||||||
"traefik.service.frontend.passHostHeader": "false",
|
"traefik.service.frontend.passHostHeader": "false",
|
||||||
"traefik.service.frontend.rule": "Path:/mypath",
|
"traefik.service.frontend.rule": "Path:/mypath",
|
||||||
"traefik.service.frontend.priority": "5000",
|
"traefik.service.frontend.priority": "5000",
|
||||||
|
@ -595,7 +595,7 @@ func TestDockerGetServiceBackendNameV1(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
container: containerJSON(labels(map[string]string{
|
container: containerJSON(labels(map[string]string{
|
||||||
"traefik.myservice.frontend.backend": "custom-backend",
|
"traefik.myservice.backend": "custom-backend",
|
||||||
})),
|
})),
|
||||||
expected: "fake-custom-backend",
|
expected: "fake-custom-backend",
|
||||||
},
|
},
|
||||||
|
|
|
@ -59,6 +59,7 @@ func GetBoolValue(labels map[string]string, labelName string, defaultValue bool)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
log.Errorf("Unable to parse %q: %q, falling back to %v. %v", labelName, rawValue, defaultValue, err)
|
||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,6 @@ const (
|
||||||
SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression"
|
SuffixBackendBufferingRetryExpression = SuffixBackendBuffering + ".retryExpression"
|
||||||
SuffixFrontend = "frontend"
|
SuffixFrontend = "frontend"
|
||||||
SuffixFrontendAuthBasic = "frontend.auth.basic"
|
SuffixFrontendAuthBasic = "frontend.auth.basic"
|
||||||
SuffixFrontendBackend = "frontend.backend"
|
|
||||||
SuffixFrontendEntryPoints = "frontend.entryPoints"
|
SuffixFrontendEntryPoints = "frontend.entryPoints"
|
||||||
SuffixFrontendHeaders = "frontend.headers."
|
SuffixFrontendHeaders = "frontend.headers."
|
||||||
SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders"
|
SuffixFrontendRequestHeaders = SuffixFrontendHeaders + "customRequestHeaders"
|
||||||
|
@ -105,7 +104,6 @@ const (
|
||||||
TraefikBackendBufferingRetryExpression = Prefix + SuffixBackendBufferingRetryExpression
|
TraefikBackendBufferingRetryExpression = Prefix + SuffixBackendBufferingRetryExpression
|
||||||
TraefikFrontend = Prefix + SuffixFrontend
|
TraefikFrontend = Prefix + SuffixFrontend
|
||||||
TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic
|
TraefikFrontendAuthBasic = Prefix + SuffixFrontendAuthBasic
|
||||||
TraefikFrontendBackend = Prefix + SuffixFrontendBackend
|
|
||||||
TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints
|
TraefikFrontendEntryPoints = Prefix + SuffixFrontendEntryPoints
|
||||||
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
|
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
|
||||||
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert
|
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert
|
||||||
|
|
|
@ -150,7 +150,7 @@ func getBackendName(service rancherData) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSegmentBackendName(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)
|
return provider.Normalize(service.Name + "-" + value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"root": "src",
|
"root": "src",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"assets": [
|
"assets": [
|
||||||
"assets",
|
"assets/images",
|
||||||
"favicon.ico"
|
"favicon.ico"
|
||||||
],
|
],
|
||||||
"index": "index.html",
|
"index": "index.html",
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
"testTsconfig": "tsconfig.spec.json",
|
"testTsconfig": "tsconfig.spec.json",
|
||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
"styles": [
|
"styles": [
|
||||||
"styles/app.sass"
|
"app.sass"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"../node_modules/@fortawesome/fontawesome/index.js",
|
"../node_modules/@fortawesome/fontawesome/index.js",
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"@angular/router": "^5.2.0",
|
"@angular/router": "^5.2.0",
|
||||||
"@fortawesome/fontawesome": "^1.1.5",
|
"@fortawesome/fontawesome": "^1.1.5",
|
||||||
"@fortawesome/fontawesome-free-solid": "^5.0.10",
|
"@fortawesome/fontawesome-free-solid": "^5.0.10",
|
||||||
"bulma": "^0.6.2",
|
"bulma": "^0.7.0",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"d3": "^4.13.0",
|
"d3": "^4.13.0",
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
|
|
27
webui/src/app.sass
Normal file
27
webui/src/app.sass
Normal file
|
@ -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
|
|
@ -1,4 +1,4 @@
|
||||||
import { TestBed, async } from '@angular/core/testing';
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
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 { ApiService } from './services/api.service';
|
||||||
import { WindowService } from './services/window.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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -22,7 +25,10 @@ import { KeysPipe } from './pipes/keys.pipe';
|
||||||
HealthComponent,
|
HealthComponent,
|
||||||
LineChartComponent,
|
LineChartComponent,
|
||||||
BarChartComponent,
|
BarChartComponent,
|
||||||
KeysPipe
|
KeysPipe,
|
||||||
|
FrontendFilterPipe,
|
||||||
|
BackendFilterPipe,
|
||||||
|
LetDirective
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -30,8 +36,8 @@ import { KeysPipe } from './pipes/keys.pipe';
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
RouterModule.forRoot([
|
RouterModule.forRoot([
|
||||||
{ path: '', component: ProvidersComponent, pathMatch: 'full' },
|
{path: '', component: ProvidersComponent, pathMatch: 'full'},
|
||||||
{ path: 'status', component: HealthComponent }
|
{path: 'status', component: HealthComponent}
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -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 { WindowService } from '../../services/window.service';
|
||||||
import {
|
|
||||||
min,
|
|
||||||
max,
|
|
||||||
easeLinear,
|
|
||||||
select,
|
|
||||||
axisLeft,
|
|
||||||
axisBottom,
|
|
||||||
scaleBand,
|
|
||||||
scaleLinear
|
|
||||||
} from 'd3';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-bar-chart',
|
selector: 'app-bar-chart',
|
||||||
|
@ -23,12 +15,12 @@ export class BarChartComponent implements OnInit, OnChanges {
|
||||||
x: any;
|
x: any;
|
||||||
y: any;
|
y: any;
|
||||||
g: any;
|
g: any;
|
||||||
bars: any;
|
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
margin = { top: 40, right: 40, bottom: 40, left: 40 };
|
margin = {top: 40, right: 40, bottom: 40, left: 40};
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
data: any[];
|
data: any[];
|
||||||
|
previousData: any[];
|
||||||
|
|
||||||
constructor(public elementRef: ElementRef, public windowService: WindowService) {
|
constructor(public elementRef: ElementRef, public windowService: WindowService) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
@ -37,7 +29,7 @@ export class BarChartComponent implements OnInit, OnChanges {
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart');
|
this.barChartEl = this.elementRef.nativeElement.querySelector('.bar-chart');
|
||||||
this.setup();
|
this.setup();
|
||||||
setTimeout(() => this.loading = false, 4000);
|
setTimeout(() => this.loading = false, 1000);
|
||||||
|
|
||||||
this.windowService.resize.subscribe(w => this.draw());
|
this.windowService.resize.subscribe(w => this.draw());
|
||||||
}
|
}
|
||||||
|
@ -47,15 +39,20 @@ export class BarChartComponent implements OnInit, OnChanges {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data = this.value;
|
if (!_.isEqual(this.previousData, this.value)) {
|
||||||
this.draw();
|
this.previousData = _.cloneDeep(this.value);
|
||||||
|
this.data = this.value;
|
||||||
|
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setup(): void {
|
setup(): void {
|
||||||
this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right;
|
this.width = this.barChartEl.clientWidth - this.margin.left - this.margin.right;
|
||||||
this.height = this.barChartEl.clientHeight - this.margin.top - this.margin.bottom;
|
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('width', this.width + this.margin.left + this.margin.right)
|
||||||
.attr('height', this.height + this.margin.top + this.margin.bottom);
|
.attr('height', this.height + this.margin.top + this.margin.bottom);
|
||||||
|
|
||||||
|
@ -73,11 +70,16 @@ export class BarChartComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(): void {
|
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.x.domain(this.data.map((d: any) => d.code));
|
||||||
this.y.domain([0, max(this.data, (d: any) => d.count)]);
|
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
|
this.svg
|
||||||
.attr('width', this.width + this.margin.left + this.margin.right)
|
.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')
|
this.g.select('.axis--y')
|
||||||
.call(axisLeft(this.y).tickSize(-this.width));
|
.call(axisLeft(this.y).tickSize(-this.width));
|
||||||
|
|
||||||
|
// Clean previous graph
|
||||||
|
this.g.selectAll('.bar').remove();
|
||||||
|
|
||||||
const bars = this.g.selectAll('.bar').data(this.data);
|
const bars = this.g.selectAll('.bar').data(this.data);
|
||||||
|
|
||||||
bars.enter()
|
bars.enter()
|
||||||
.append('rect')
|
.append('rect')
|
||||||
.attr('class', 'bar')
|
.attr('class', 'bar')
|
||||||
.attr('x', (d: any) => d.code)
|
.style('fill', (d: any) => 'hsl(' + Math.floor(((d.code - 100) * 310 / 427) + 50) + ', 50%, 50%)')
|
||||||
.attr('y', (d: any) => d.count)
|
.attr('x', (d: any) => this.x(d.code))
|
||||||
.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))
|
|
||||||
.attr('y', (d: any) => this.y(d.count))
|
.attr('y', (d: any) => this.y(d.count))
|
||||||
.attr('width', this.x.bandwidth())
|
.attr('width', this.x.bandwidth())
|
||||||
.attr('height', (d: any) => (this.height - this.y(d.count)) < 0 ? 0 : this.height - this.y(d.count));
|
.attr('height', (d: any) => (this.height - this.y(d.count)) < 0 ? 0 : this.height - this.y(d.count));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="line-chart" [class.is-hidden]="loading"></div>
|
<div class="line-chart" [class.is-hidden]="loading"></div>
|
||||||
<div class="loading-text" [class.is-hidden]="!loading">
|
<div class="loading-text line-chart-loading" [class.is-hidden]="!loading">
|
||||||
<span>
|
<span>
|
||||||
<span>Loading, please wait...</span>
|
<span>Loading, please wait...</span>
|
||||||
<img src="./assets/images/loader.svg" class="main-loader">
|
<img src="./assets/images/loader.svg" class="main-loader">
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { Component, Input, OnInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
import { WindowService } from '../../services/window.service';
|
|
||||||
import {
|
import {
|
||||||
range,
|
|
||||||
scaleTime,
|
|
||||||
scaleLinear,
|
|
||||||
min,
|
|
||||||
max,
|
|
||||||
curveLinear,
|
|
||||||
line,
|
|
||||||
easeLinear,
|
|
||||||
select,
|
|
||||||
axisLeft,
|
|
||||||
axisBottom,
|
axisBottom,
|
||||||
timeSecond,
|
axisLeft,
|
||||||
timeFormat
|
curveLinear,
|
||||||
|
easeLinear,
|
||||||
|
line,
|
||||||
|
max,
|
||||||
|
min,
|
||||||
|
range,
|
||||||
|
scaleLinear,
|
||||||
|
scaleTime,
|
||||||
|
select,
|
||||||
|
timeFormat,
|
||||||
|
timeSecond
|
||||||
} from 'd3';
|
} from 'd3';
|
||||||
|
import { WindowService } from '../../services/window.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-line-chart',
|
selector: 'app-line-chart',
|
||||||
|
@ -23,7 +23,10 @@ import {
|
||||||
export class LineChartComponent implements OnChanges, OnInit {
|
export class LineChartComponent implements OnChanges, OnInit {
|
||||||
@Input() value: { count: number, date: string };
|
@Input() value: { count: number, date: string };
|
||||||
|
|
||||||
|
firstDisplay: boolean;
|
||||||
|
dirty: boolean;
|
||||||
lineChartEl: HTMLElement;
|
lineChartEl: HTMLElement;
|
||||||
|
loadingEl: HTMLElement;
|
||||||
svg: any;
|
svg: any;
|
||||||
g: any;
|
g: any;
|
||||||
line: any;
|
line: any;
|
||||||
|
@ -39,15 +42,19 @@ export class LineChartComponent implements OnChanges, OnInit {
|
||||||
yAxis: any;
|
yAxis: any;
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
margin = { top: 40, right: 40, bottom: 60, left: 60 };
|
margin = {top: 40, right: 40, bottom: 60, left: 60};
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
constructor(private elementRef: ElementRef, public windowService: WindowService) { }
|
constructor(private elementRef: ElementRef, public windowService: WindowService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.lineChartEl = this.elementRef.nativeElement.querySelector('.line-chart');
|
this.lineChartEl = this.elementRef.nativeElement.querySelector('.line-chart');
|
||||||
|
this.loadingEl = this.elementRef.nativeElement.querySelector('.line-chart-loading');
|
||||||
this.limit = 40;
|
this.limit = 40;
|
||||||
|
|
||||||
|
// related to the Observable.timer(0, 3000) in health component
|
||||||
this.duration = 3000;
|
this.duration = 3000;
|
||||||
|
|
||||||
this.now = new Date(Date.now() - this.duration);
|
this.now = new Date(Date.now() - this.duration);
|
||||||
|
|
||||||
this.options = {
|
this.options = {
|
||||||
|
@ -55,22 +62,37 @@ export class LineChartComponent implements OnChanges, OnInit {
|
||||||
color: '#3A84C5'
|
color: '#3A84C5'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.firstDisplay = true;
|
||||||
this.render();
|
this.render();
|
||||||
setTimeout(() => this.loading = false, 4000);
|
|
||||||
this.windowService.resize.subscribe(w => {
|
this.windowService.resize.subscribe(w => {
|
||||||
if (this.svg) {
|
if (this.svg) {
|
||||||
const el = this.lineChartEl.querySelector('svg');
|
this.dirty = true;
|
||||||
el.parentNode.removeChild(el);
|
this.loading = true;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
this.width = this.lineChartEl.clientWidth - this.margin.left - this.margin.right;
|
// When the lineChartEl is not displayed (is-hidden), width and length are equal to 0.
|
||||||
this.height = this.lineChartEl.clientHeight - this.margin.top - this.margin.bottom;
|
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('width', this.width + this.margin.left + this.margin.right)
|
||||||
.attr('height', this.height + this.margin.top + this.margin.bottom)
|
.attr('height', this.height + this.margin.top + this.margin.bottom)
|
||||||
.append('g')
|
.append('g')
|
||||||
|
@ -80,7 +102,7 @@ export class LineChartComponent implements OnChanges, OnInit {
|
||||||
this.data = range(this.limit).map(i => 0);
|
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.y = scaleLinear().range([this.height, 0]);
|
||||||
|
|
||||||
this.x.domain([<any>this.now - (this.limit - 2), <any>this.now - this.duration]);
|
this.x.domain([<any>this.now - (this.limit - 2), <any>this.now - this.duration]);
|
||||||
|
@ -91,7 +113,9 @@ export class LineChartComponent implements OnChanges, OnInit {
|
||||||
.y((d: any) => this.y(d))
|
.y((d: any) => this.y(d))
|
||||||
.curve(curveLinear);
|
.curve(curveLinear);
|
||||||
|
|
||||||
this.svg.append('defs').append('clipPath')
|
this.svg
|
||||||
|
.append('defs')
|
||||||
|
.append('clipPath')
|
||||||
.attr('id', 'clip')
|
.attr('id', 'clip')
|
||||||
.append('rect')
|
.append('rect')
|
||||||
.attr('width', this.width)
|
.attr('width', this.width)
|
||||||
|
@ -121,7 +145,7 @@ export class LineChartComponent implements OnChanges, OnInit {
|
||||||
this.updateData(this.value.count);
|
this.updateData(this.value.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateData = (value: number) => {
|
updateData(value: number) {
|
||||||
this.data.push(value * 1000000);
|
this.data.push(value * 1000000);
|
||||||
this.now = new Date();
|
this.now = new Date();
|
||||||
|
|
||||||
|
@ -132,9 +156,13 @@ export class LineChartComponent implements OnChanges, OnInit {
|
||||||
|
|
||||||
this.xAxis
|
this.xAxis
|
||||||
.transition()
|
.transition()
|
||||||
.duration(this.duration)
|
.duration(this.firstDisplay || this.dirty ? 0 : this.duration)
|
||||||
.ease(easeLinear)
|
.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')
|
.selectAll('text')
|
||||||
.style('text-anchor', 'end')
|
.style('text-anchor', 'end')
|
||||||
.attr('dx', '-.8em')
|
.attr('dx', '-.8em')
|
||||||
|
@ -157,6 +185,13 @@ export class LineChartComponent implements OnChanges, OnInit {
|
||||||
.ease(easeLinear)
|
.ease(easeLinear)
|
||||||
.attr('transform', `translate(${this.x(<any>this.now - (this.limit - 1) * this.duration)})`);
|
.attr('transform', `translate(${this.x(<any>this.now - (this.limit - 1) * this.duration)})`);
|
||||||
|
|
||||||
|
this.firstDisplay = false;
|
||||||
|
this.dirty = false;
|
||||||
|
|
||||||
|
if (this.loading) {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
this.data.shift();
|
this.data.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">
|
<nav class="navbar is-fixed-top is-transparent" role="navigation" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-menu">
|
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item" routerLink="/">
|
<a class="navbar-item" routerLink="/" (click)="burger = false">
|
||||||
<img src="./assets/images/traefik.logo.svg" alt="Traefik" class="navbar-logo">
|
<img src="./assets/images/traefik.logo.svg" alt="Traefik" class="navbar-logo">
|
||||||
|
</a>
|
||||||
|
<div class="navbar-burger burger" data-target="navbarMain" (click)="burger = !burger" [class.is-active]="burger">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="navbarMain" class="navbar-menu" [class.is-active]="burger">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" routerLink="/" routerLinkActive="is-active" [routerLinkActiveOptions]="{ exact: true }" (click)="burger = false">
|
||||||
|
Providers
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" routerLink="/status" routerLinkActive="is-active" (click)="burger = false">
|
||||||
|
Health
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-start">
|
<div class="navbar-end">
|
||||||
<div class="navbar-menu">
|
|
||||||
<a class="navbar-item" routerLink="/" routerLinkActive="is-active" [routerLinkActiveOptions]="{ exact: true }">
|
|
||||||
Providers
|
|
||||||
</a>
|
|
||||||
<a class="navbar-item" routerLink="/status" routerLinkActive="is-active">
|
|
||||||
Health
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end is-hidden-mobile">
|
|
||||||
<a class="navbar-item" [href]="releaseLink" target="_blank">
|
<a class="navbar-item" [href]="releaseLink" target="_blank">
|
||||||
{{ version }} / {{ codename }}
|
{{ version }} / {{ codename }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -25,5 +30,6 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -9,6 +9,7 @@ export class HeaderComponent implements OnInit {
|
||||||
version: string;
|
version: string;
|
||||||
codename: string;
|
codename: string;
|
||||||
releaseLink: string;
|
releaseLink: string;
|
||||||
|
burger: boolean;
|
||||||
|
|
||||||
constructor(private apiService: ApiService) { }
|
constructor(private apiService: ApiService) { }
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="column is-4">
|
<div class="column is-4">
|
||||||
<div class="item-data border-right">
|
<div class="item-data border-right">
|
||||||
<span class="data-grey">Total Response Time</span>
|
<span class="data-grey">Total Response Time</span>
|
||||||
<span class="data-blue">{{ totalResponseTime }}</span>
|
<span class="data-blue" [title]="exactTotalResponseTime">{{ totalResponseTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-4">
|
<div class="column is-4">
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
<div class="column is-4">
|
<div class="column is-4">
|
||||||
<div class="item-data border-right">
|
<div class="item-data border-right">
|
||||||
<span class="data-grey">Average Response Time</span>
|
<span class="data-grey">Average Response Time</span>
|
||||||
<span class="data-blue">{{ averageResponseTime }}</span>
|
<span class="data-blue" [title]="exactAverageResponseTime">{{ averageResponseTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-4">
|
<div class="column is-4">
|
||||||
|
@ -82,15 +82,15 @@
|
||||||
<td>Request</td>
|
<td>Request</td>
|
||||||
<td>Time</td>
|
<td>Time</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngFor="let entry of recentErrors">
|
<tr *ngFor="let entry of recentErrors; trackBy: trackRecentErrors;">
|
||||||
<td>
|
<td>
|
||||||
<span class="tag is-info">{{ entry.status_code }}</span> <span>{{ entry.status }}</span>
|
<span class="tag is-info" [title]="entry.status">{{ entry.status_code }}</span> <span class="is-hidden-mobile is-hidden-desktop-only">{{ entry.status }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="tag">{{ entry.method }}</span> <a>{{ entry.host }}{{ entry.path }}</a>
|
<span class="tag">{{ entry.method }}</span> <span>{{ entry.host }}{{ entry.path }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span>{{ entry.time }}</span>
|
<span [title]="entry.time | date:'yyyy-MM-dd HH:mm:ss:SSS a z'">{{ entry.time | date:'yyyy-MM-dd HH:mm:ss a z' }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="!recentErrors?.length">
|
<tr *ngIf="!recentErrors?.length">
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { distanceInWordsStrict, format, subSeconds } from 'date-fns';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import 'rxjs/add/observable/timer';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/mergeMap';
|
||||||
|
import 'rxjs/add/operator/timeInterval';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import 'rxjs/add/observable/timer';
|
import { ApiService } from '../../services/api.service';
|
||||||
import 'rxjs/add/operator/timeInterval';
|
|
||||||
import 'rxjs/add/operator/mergeMap';
|
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
import { format, distanceInWordsStrict, subSeconds } from 'date-fns';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-health',
|
selector: 'app-health',
|
||||||
|
@ -15,11 +16,14 @@ import { format, distanceInWordsStrict, subSeconds } from 'date-fns';
|
||||||
export class HealthComponent implements OnInit, OnDestroy {
|
export class HealthComponent implements OnInit, OnDestroy {
|
||||||
sub: Subscription;
|
sub: Subscription;
|
||||||
recentErrors: any;
|
recentErrors: any;
|
||||||
|
previousRecentErrors: any;
|
||||||
pid: number;
|
pid: number;
|
||||||
uptime: string;
|
uptime: string;
|
||||||
uptimeSince: string;
|
uptimeSince: string;
|
||||||
averageResponseTime: string;
|
averageResponseTime: string;
|
||||||
|
exactAverageResponseTime: string;
|
||||||
totalResponseTime: string;
|
totalResponseTime: string;
|
||||||
|
exactTotalResponseTime: string;
|
||||||
codeCount: number;
|
codeCount: number;
|
||||||
totalCodeCount: number;
|
totalCodeCount: number;
|
||||||
chartValue: any;
|
chartValue: any;
|
||||||
|
@ -33,16 +37,22 @@ export class HealthComponent implements OnInit, OnDestroy {
|
||||||
.mergeMap(() => this.apiService.fetchHealthStatus())
|
.mergeMap(() => this.apiService.fetchHealthStatus())
|
||||||
.subscribe(data => {
|
.subscribe(data => {
|
||||||
if (data) {
|
if (data) {
|
||||||
this.recentErrors = data.recent_errors;
|
if (!_.isEqual(this.previousRecentErrors, data.recent_errors)) {
|
||||||
this.chartValue = { count: data.average_response_time_sec, date: data.time };
|
this.previousRecentErrors = _.cloneDeep(data.recent_errors);
|
||||||
|
this.recentErrors = data.recent_errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chartValue = {count: data.average_response_time_sec, date: data.time};
|
||||||
this.statusCodeValue = Object.keys(data.total_status_code_count)
|
this.statusCodeValue = Object.keys(data.total_status_code_count)
|
||||||
.map(key => ({ code: key, count: data.total_status_code_count[key] }));
|
.map(key => ({code: key, count: data.total_status_code_count[key]}));
|
||||||
|
|
||||||
this.pid = data.pid;
|
this.pid = data.pid;
|
||||||
this.uptime = distanceInWordsStrict(subSeconds(new Date(), data.uptime_sec), new Date());
|
this.uptime = distanceInWordsStrict(subSeconds(new Date(), data.uptime_sec), new Date());
|
||||||
this.uptimeSince = format(subSeconds(new Date(), data.uptime_sec), 'MM/DD/YYYY HH:mm:ss');
|
this.uptimeSince = format(subSeconds(new Date(), data.uptime_sec), 'YYYY-MM-DD HH:mm:ss Z');
|
||||||
this.totalResponseTime = data.total_response_time;
|
this.totalResponseTime = distanceInWordsStrict(subSeconds(new Date(), data.total_response_time_sec), new Date());
|
||||||
this.averageResponseTime = data.average_response_time;
|
this.exactTotalResponseTime = data.total_response_time;
|
||||||
|
this.averageResponseTime = Math.floor(data.average_response_time_sec * 1000) + ' ms';
|
||||||
|
this.exactAverageResponseTime = data.average_response_time;
|
||||||
this.codeCount = data.count;
|
this.codeCount = data.count;
|
||||||
this.totalCodeCount = data.total_count;
|
this.totalCodeCount = data.total_count;
|
||||||
}
|
}
|
||||||
|
@ -54,4 +64,8 @@ export class HealthComponent implements OnInit, OnDestroy {
|
||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trackRecentErrors(index, item): string {
|
||||||
|
return item.status_code + item.method + item.host + item.path + item.time;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
<div class="column is-12">
|
<div class="column is-12">
|
||||||
|
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<span class="icon"><i class="fas fa-search"></i></span>
|
<span class="icon search-button" *ngIf="!keyword"><i class="fas fa-search"></i></span>
|
||||||
<input type="text" placeholder="Filter by name or id ..." [(ngModel)]="keyword" (ngModelChange)="filter()">
|
<a class="delete search-button" *ngIf="keyword" (click)="keyword = ''"></a>
|
||||||
|
<input type="text" placeholder="Filter by name or id ..." [(ngModel)]="keyword">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tabs" *ngIf="keys?.length">
|
<div class="tabs" *ngIf="keys?.length">
|
||||||
|
@ -20,30 +21,17 @@
|
||||||
<div *ngIf="keys?.length">
|
<div *ngIf="keys?.length">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<!-- Frontends -->
|
<!-- Frontends -->
|
||||||
<div class="column is-6">
|
<div class="column is-6" *appLet="providers[tab]?.frontends | frontendFilter:keyword as frontends">
|
||||||
<h2 class="subtitle"><span class="tag is-info">{{ providers[tab]?.frontends.length }}</span> Frontends</h2>
|
<h2 class="subtitle"><span class="tag is-info">{{ frontends.length }}</span><span class="subtitle-name">Frontends</span></h2>
|
||||||
<div class="message" *ngFor="let p of providers[tab]?.frontends; let i = index;">
|
|
||||||
<div class="message-header">
|
<div *ngIf="frontends.length < maxItem">
|
||||||
|
|
||||||
|
<div class="message" *ngFor="let p of frontends; trackBy: trackItem(tab)">
|
||||||
|
<div class="message-header" [class.has-background-info]="p.backend" [class.has-background-danger]="!p.backend">
|
||||||
<h2>
|
<h2>
|
||||||
|
<i class="icon fas fa-globe has-text-white"></i>
|
||||||
<div>
|
<div>
|
||||||
<i class="icon fas fa-globe"></i>
|
<span class="has-text-white" [class.is-info]="p.backend" [class.is-danger]="!p.backend">{{ p.id }}</span>
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
|
||||||
<div class="control">
|
|
||||||
<div class="tags has-addons">
|
|
||||||
<span class="tag is-info">{{ p.id }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="p.backend">
|
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
|
||||||
<div class="control">
|
|
||||||
<a class="tags has-addons" [href]="'#' + p.backend">
|
|
||||||
<span class="tag is-light">Backend</span>
|
|
||||||
<span class="tag is-primary">{{ p.backend }}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,16 +45,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<div *ngIf="p.section !== 'details'">
|
<div *ngIf="p.section !== 'details'" class="section-container">
|
||||||
|
|
||||||
<div *ngIf="p.routes && p.routes.length">
|
<div *ngIf="p.routes && p.routes.length" class="section-line">
|
||||||
|
<div>
|
||||||
|
<h2>Route Rule</h2>
|
||||||
|
</div>
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<table class="table is-fullwidth is-hoverable">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr *ngFor="let route of p.routes">
|
||||||
<td>Route Rule</td>
|
<td><code class="has-text-grey" [title]="route.id">{{ route.rule }}</code></td>
|
||||||
</tr>
|
|
||||||
<tr *ngFor="let route of p.routes; let ri = index;">
|
|
||||||
<td><code class="has-text-grey" title="{{ route.title }}">{{ route.rule }}</code></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -74,15 +62,15 @@
|
||||||
|
|
||||||
<div *ngIf="p.entryPoints && p.entryPoints.length">
|
<div *ngIf="p.entryPoints && p.entryPoints.length">
|
||||||
<hr>
|
<hr>
|
||||||
<div class="columns">
|
<div class="columns section-line">
|
||||||
<div class="column is-3">
|
<div class="column is-3">
|
||||||
<h2>Entry Points</h2>
|
<h2 class="section-line-header">Entry Points</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<span class="tag is-info" *ngFor="let ep of p.entryPoints; let ri = index;">{{ ep }}</span>
|
<span class="tag is-info" *ngFor="let ep of p.entryPoints">{{ ep }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,19 +78,34 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="p.backend">
|
||||||
|
<hr>
|
||||||
|
<div class="columns section-line">
|
||||||
|
<div class="column is-2">
|
||||||
|
<h2 class="section-line-header">Backend</h2>
|
||||||
|
</div>
|
||||||
|
<div class="column is-10">
|
||||||
|
<div class="field">
|
||||||
|
<i class="icon fas fa-server has-text-primary" title="Backend"></i>
|
||||||
|
<span class="has-text-primary">{{ p.backend }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Details -->
|
<!-- Details -->
|
||||||
<div *ngIf="p.section === 'details'">
|
<div *ngIf="p.section === 'details'" class="section-container">
|
||||||
|
|
||||||
<div>
|
<div class="section-line">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-3">
|
<div class="column is-3">
|
||||||
<h2>Misc.</h2>
|
<h2 class="section-line-header">Misc.</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<div class="control">
|
<div class="control" *ngIf="p.priority">
|
||||||
<div class="tags has-addons">
|
<div class="tags has-addons">
|
||||||
<span class="tag is-light">Priority</span>
|
<span class="tag is-light">Priority</span>
|
||||||
<span class="tag is-info">{{ p.priority }}</span>
|
<span class="tag is-info">{{ p.priority }}</span>
|
||||||
|
@ -111,7 +114,7 @@
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="tags has-addons">
|
<div class="tags has-addons">
|
||||||
<span class="tag is-light">Host Header</span>
|
<span class="tag is-light">Host Header</span>
|
||||||
<span class="tag is-info">{{ p.passHostHeader }}</span>
|
<span class="tag is-info">{{ !!p.passHostHeader }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control" *ngIf="p.passTLSCert">
|
<div class="control" *ngIf="p.passTLSCert">
|
||||||
|
@ -127,9 +130,9 @@
|
||||||
|
|
||||||
<div *ngIf="p.redirect">
|
<div *ngIf="p.redirect">
|
||||||
<hr>
|
<hr>
|
||||||
<div class="columns">
|
<div class="columns section-line">
|
||||||
<div class="column is-3">
|
<div class="column is-3">
|
||||||
<h2>Redirect</h2>
|
<h2 class="section-line-header">Redirect</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<div class="field is-grouped is-grouped-multiline" *ngIf="p.redirect.entryPoint">
|
<div class="field is-grouped is-grouped-multiline" *ngIf="p.redirect.entryPoint">
|
||||||
|
@ -160,45 +163,49 @@
|
||||||
|
|
||||||
<div *ngIf="p.basicAuth && p.basicAuth.length">
|
<div *ngIf="p.basicAuth && p.basicAuth.length">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h2>Basic Authentication</h2>
|
<div class="section-line">
|
||||||
<div class="tags padding-5-10">
|
<h2 class="section-line-header">Basic Authentication</h2>
|
||||||
<span class="tag is-info" *ngFor="let auth of p.basicAuth; let ri = index;">{{ auth }}</span>
|
<div class="tags padding-5-10">
|
||||||
|
<span class="tag is-info" *ngFor="let auth of p.basicAuth">{{ auth }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="p.errors">
|
<div *ngIf="p.errors?.length">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h2>Error Pages</h2>
|
<div class="section-line">
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<h2 class="section-line-header">Error Pages</h2>
|
||||||
<tbody>
|
<table class="table is-fullwidth is-hoverable">
|
||||||
<tr>
|
<tbody>
|
||||||
<td>Backend</td>
|
<tr>
|
||||||
<td>Query</td>
|
<td>Backend</td>
|
||||||
<td>Status</td>
|
<td>Query</td>
|
||||||
</tr>
|
<td>Status</td>
|
||||||
<tr *ngFor="let key of p.errors | keys">
|
</tr>
|
||||||
<td><span class="has-text-grey-light">{{ p.errors[key].backend }}</span></td>
|
<tr *ngFor="let entry of p.errors">
|
||||||
<td><span class="has-text-grey">{{ p.errors[key].query }}</span></td>
|
<td><span class="has-text-grey-light">{{ entry.backend }}</span></td>
|
||||||
<td>
|
<td><span class="has-text-grey">{{ entry.query }}</span></td>
|
||||||
<span class="tag is-light" *ngFor="let state of p.errors[key].status">{{ state }}</span>
|
<td>
|
||||||
</td>
|
<span class="tag is-light" *ngFor="let state of entry.status">{{ state }}</span>
|
||||||
</tr>
|
</td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="p.whiteList">
|
<div *ngIf="p.whiteList">
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="columns is-gapless is-multiline is-mobile">
|
<div class="columns is-gapless is-multiline is-mobile section-line">
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<h2>Whitelist</h2>
|
<h2 class="section-line-header">Whitelist</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="tags has-addons">
|
<div class="tags has-addons">
|
||||||
<span class="tag is-light">useXForwardedFor</span>
|
<span class="tag is-light">useXForwardedFor</span>
|
||||||
<span class="tag is-info">{{ p.whiteList.useXForwardedFor }}</span>
|
<span class="tag is-info">{{ !!p.whiteList.useXForwardedFor }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -207,7 +214,7 @@
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<span class="tag is-info" *ngFor="let wlRange of p.whiteList.sourceRange; let ri = index;">{{ wlRange }}</span>
|
<span class="tag is-info" *ngFor="let wlRange of p.whiteList.sourceRange">{{ wlRange }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -217,126 +224,137 @@
|
||||||
|
|
||||||
<div *ngIf="p.headers">
|
<div *ngIf="p.headers">
|
||||||
<hr/>
|
<hr/>
|
||||||
<h2>Headers</h2>
|
<div class="section-line">
|
||||||
<div class="columns is-multiline">
|
<h2 class="section-line-header">Headers</h2>
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
|
||||||
<div class="column is-12" *ngIf="p.headers.customRequestHeaders">
|
<div class="column is-12" *ngIf="p.headers.customRequestHeaders?.length">
|
||||||
<h2>Custom Request Headers</h2>
|
<table class="table is-fullwidth is-hoverable table-fixed-break">
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr *ngFor="let key of p.headers.customRequestHeaders | keys">
|
<td colspan="2">Custom Request Headers</td>
|
||||||
<td><span class="has-text-grey-light">{{ key }}</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.customRequestHeaders[key] }}</span></td>
|
<tr *ngFor="let header of p.headers.customRequestHeaders">
|
||||||
</tr>
|
<td><span class="has-text-grey-light">{{ header.name }}</span></td>
|
||||||
</tbody>
|
<td><span class="has-text-grey">{{ header.value }}</span></td>
|
||||||
</table>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="column is-12" *ngIf="p.headers.customResponseHeaders">
|
<div class="column is-12" *ngIf="p.headers.customResponseHeaders?.length">
|
||||||
<h2>Custom Response Headers</h2>
|
<table class="table is-fullwidth is-hoverable table-fixed-break">
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr *ngFor="let key of p.headers.customResponseHeaders | keys">
|
<td colspan="2">Custom Response Headers</td>
|
||||||
<td><span class="has-text-grey-light">{{ key }}</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.customResponseHeaders[key] }}</span></td>
|
<tr *ngFor="let header of p.headers.customResponseHeaders">
|
||||||
</tr>
|
<td><span class="has-text-grey-light">{{ header.name }}</span></td>
|
||||||
</tbody>
|
<td><span class="has-text-grey">{{ header.value }}</span></td>
|
||||||
</table>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="column is-12">
|
<div class="column is-12">
|
||||||
<h2>Secure</h2>
|
<table class="table is-fullwidth is-hoverable table-fixed-break">
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<tbody>
|
||||||
<tbody>
|
<tr>
|
||||||
<tr *ngIf="p.headers.browserXssFilter">
|
<td colspan="2">Secure</td>
|
||||||
<td><span class="has-text-grey">Browser XSS Filter</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.browserXssFilter }}</span></td>
|
<tr *ngIf="p.headers.browserXssFilter">
|
||||||
</tr>
|
<td><span class="has-text-grey">Browser XSS Filter</span></td>
|
||||||
<tr *ngIf="p.headers.contentSecurityPolicy">
|
<td><span class="has-text-grey">{{ p.headers.browserXssFilter }}</span></td>
|
||||||
<td><span class="has-text-grey">Content Security Policy</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.contentSecurityPolicy }}</span></td>
|
<tr *ngIf="p.headers.contentSecurityPolicy">
|
||||||
</tr>
|
<td><span class="has-text-grey">Content Security Policy</span></td>
|
||||||
<tr *ngIf="p.headers.contentTypeNoSniff">
|
<td><span class="has-text-grey">{{ p.headers.contentSecurityPolicy }}</span></td>
|
||||||
<td><span class="has-text-grey">Content Type (No sniff)</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.contentTypeNoSniff }}</span></td>
|
<tr *ngIf="p.headers.contentTypeNoSniff">
|
||||||
</tr>
|
<td><span class="has-text-grey">Content Type (No sniff)</span></td>
|
||||||
<tr *ngIf="p.headers.customFrameOptionsValue">
|
<td><span class="has-text-grey">{{ p.headers.contentTypeNoSniff }}</span></td>
|
||||||
<td><span class="has-text-grey">Custom Frame Options Value</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.customFrameOptionsValue }}</span></td>
|
<tr *ngIf="p.headers.customFrameOptionsValue">
|
||||||
</tr>
|
<td><span class="has-text-grey">Custom Frame Options Value</span></td>
|
||||||
<tr *ngIf="p.headers.forceSTSHeader">
|
<td><span class="has-text-grey">{{ p.headers.customFrameOptionsValue }}</span></td>
|
||||||
<td><span class="has-text-grey">Force STS Header</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.forceSTSHeader }}</span></td>
|
<tr *ngIf="p.headers.forceSTSHeader">
|
||||||
</tr>
|
<td><span class="has-text-grey">Force STS Header</span></td>
|
||||||
<tr *ngIf="p.headers.frameDeny">
|
<td><span class="has-text-grey">{{ p.headers.forceSTSHeader }}</span></td>
|
||||||
<td><span class="has-text-grey">Frame Deny</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.frameDeny }}</span></td>
|
<tr *ngIf="p.headers.frameDeny">
|
||||||
</tr>
|
<td><span class="has-text-grey">Frame Deny</span></td>
|
||||||
<tr *ngIf="p.headers.isDevelopment">
|
<td><span class="has-text-grey">{{ p.headers.frameDeny }}</span></td>
|
||||||
<td><span class="has-text-grey">Is Development</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.isDevelopment }}</span></td>
|
<tr *ngIf="p.headers.isDevelopment">
|
||||||
</tr>
|
<td><span class="has-text-grey">Is Development</span></td>
|
||||||
<tr *ngIf="p.headers.publicKey">
|
<td><span class="has-text-grey">{{ p.headers.isDevelopment }}</span></td>
|
||||||
<td><span class="has-text-grey">Public Key</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.publicKey }}</span></td>
|
<tr *ngIf="p.headers.publicKey">
|
||||||
</tr>
|
<td><span class="has-text-grey">Public Key</span></td>
|
||||||
<tr *ngIf="p.headers.referrerPolicy">
|
<td><span class="has-text-grey">{{ p.headers.publicKey }}</span></td>
|
||||||
<td><span class="has-text-grey">Referrer Policy</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.referrerPolicy }}</span></td>
|
<tr *ngIf="p.headers.referrerPolicy">
|
||||||
</tr>
|
<td><span class="has-text-grey">Referrer Policy</span></td>
|
||||||
<tr *ngIf="p.headers.sslHost">
|
<td><span class="has-text-grey">{{ p.headers.referrerPolicy }}</span></td>
|
||||||
<td><span class="has-text-grey">SSL Host</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.sslHost }}</span></td>
|
<tr *ngIf="p.headers.sslHost">
|
||||||
</tr>
|
<td><span class="has-text-grey">SSL Host</span></td>
|
||||||
<tr *ngIf="p.headers.sslRedirect">
|
<td><span class="has-text-grey">{{ p.headers.sslHost }}</span></td>
|
||||||
<td><span class="has-text-grey">SSL Redirect</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.sslRedirect }}</span></td>
|
<tr *ngIf="p.headers.sslRedirect">
|
||||||
</tr>
|
<td><span class="has-text-grey">SSL Redirect</span></td>
|
||||||
<tr *ngIf="p.headers.sslTemporaryRedirect">
|
<td><span class="has-text-grey">{{ p.headers.sslRedirect }}</span></td>
|
||||||
<td><span class="has-text-grey">SSL Temporary Redirect</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.sslTemporaryRedirect }}</span></td>
|
<tr *ngIf="p.headers.sslTemporaryRedirect">
|
||||||
</tr>
|
<td><span class="has-text-grey">SSL Temporary Redirect</span></td>
|
||||||
<tr *ngIf="p.headers.stsIncludeSubdomains">
|
<td><span class="has-text-grey">{{ p.headers.sslTemporaryRedirect }}</span></td>
|
||||||
<td><span class="has-text-grey">STS Include Subdomains</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.stsIncludeSubdomains }}</span></td>
|
<tr *ngIf="p.headers.stsIncludeSubdomains">
|
||||||
</tr>
|
<td><span class="has-text-grey">STS Include Subdomains</span></td>
|
||||||
<tr *ngIf="p.headers.stsPreload">
|
<td><span class="has-text-grey">{{ p.headers.stsIncludeSubdomains }}</span></td>
|
||||||
<td><span class="has-text-grey">STS Preload</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.stsPreload }}</span></td>
|
<tr *ngIf="p.headers.stsPreload">
|
||||||
</tr>
|
<td><span class="has-text-grey">STS Preload</span></td>
|
||||||
<tr *ngIf="p.headers.stsSeconds">
|
<td><span class="has-text-grey">{{ p.headers.stsPreload }}</span></td>
|
||||||
<td><span class="has-text-grey">STS Seconds</span></td>
|
</tr>
|
||||||
<td><span class="has-text-grey">{{ p.headers.stsSeconds }}</span></td>
|
<tr *ngIf="p.headers.stsSeconds">
|
||||||
</tr>
|
<td><span class="has-text-grey">STS Seconds</span></td>
|
||||||
</tbody>
|
<td><span class="has-text-grey">{{ p.headers.stsSeconds }}</span></td>
|
||||||
</table>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="column is-12" *ngIf="p.headers.allowedHosts">
|
<div class="column is-12" *ngIf="p.headers.sslProxyHeaders?.length">
|
||||||
<h2>Allowed Hosts</h2>
|
<table class="table is-fullwidth is-hoverable table-fixed-break">
|
||||||
<div class="tags-list">
|
<tbody>
|
||||||
<span class="tag is-light" *ngFor="let host of p.headers.allowedHosts">{{ host }}</span>
|
<tr>
|
||||||
|
<td colspan="2">SSL Proxy Headers</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let header of p.headers.sslProxyHeaders">
|
||||||
|
<td><span class="has-text-grey-light">{{ header.name }}</span></td>
|
||||||
|
<td><span class="has-text-grey">{{ header.value }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" *ngIf="p.headers.allowedHosts">
|
||||||
|
<h2>Allowed Hosts</h2>
|
||||||
|
<div class="tags-list">
|
||||||
|
<span class="tag is-light" *ngFor="let host of p.headers.allowedHosts">{{ host }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" *ngIf="p.headers.hostsProxyHeaders">
|
||||||
|
<h2>Hosts Proxy Headers</h2>
|
||||||
|
<div class="tags-list">
|
||||||
|
<span class="tag is-light" *ngFor="let h of p.headers.hostsProxyHeaders">{{ h }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column is-12" *ngIf="p.headers.sslProxyHeaders">
|
|
||||||
<h2>SSL Proxy Headers</h2>
|
|
||||||
<table class="table is-fullwidth is-hoverable">
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let key of p.headers.sslProxyHeaders | keys">
|
|
||||||
<td><span class="has-text-grey-light">{{ key }}</span></td>
|
|
||||||
<td><span class="has-text-grey">{{ p.headers.sslProxyHeaders[key] }}</span></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column is-12" *ngIf="p.headers.hostsProxyHeaders">
|
|
||||||
<h2>Hosts Proxy Headers</h2>
|
|
||||||
<div class="tags-list">
|
|
||||||
<span class="tag is-light" *ngFor="let h of p.headers.hostsProxyHeaders">{{ h }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -344,23 +362,33 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div *ngIf="frontends.length > maxItem">
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header has-background-warning has-text-black">
|
||||||
|
Too many frontends to display, please add a filter.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Backends -->
|
<!-- Backends -->
|
||||||
<div class="column is-6">
|
<div class="column is-6" *appLet="providers[tab]?.backends | backendFilter:keyword as backends">
|
||||||
<h2 class="subtitle"><span class="tag is-primary">{{ providers[tab]?.backends.length }}</span> Backends</h2>
|
<h2 class="subtitle"><span class="tag is-primary">{{ backends.length }}</span><span class="subtitle-name">Backends</span></h2>
|
||||||
<div class="message" *ngFor="let p of providers[tab]?.backends; let i = index;">
|
|
||||||
<div class="message-header">
|
<div *ngIf="backends.length < maxItem">
|
||||||
<h2 [id]="p.id">
|
|
||||||
|
<div class="message" *ngFor="let p of backends; trackBy: trackItem(tab);">
|
||||||
|
<div class="message-header" [class.has-background-primary]="p.servers?.length" [class.has-background-danger]="!p.servers?.length">
|
||||||
|
<h2>
|
||||||
|
<i class="icon fas fa-server has-text-white"></i>
|
||||||
<div>
|
<div>
|
||||||
<i class="icon fas fa-server"></i>
|
<span class="has-text-white">{{ p.id }}</span>
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
|
||||||
<div class="control">
|
|
||||||
<div class="tags has-addons">
|
|
||||||
<span class="tag is-primary">{{ p.id }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -374,28 +402,34 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main -->
|
<!-- Main -->
|
||||||
<div *ngIf="p.section !== 'details'">
|
<div *ngIf="p.section !== 'details'" class="section-container">
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<div class="section-line">
|
||||||
<tbody>
|
<table class="table is-fullwidth is-hoverable table-fixed">
|
||||||
<tr>
|
<colgroup>
|
||||||
<td>Server</td>
|
<col class="table-col-75">
|
||||||
<td>Weight</td>
|
<col>
|
||||||
</tr>
|
</colgroup>
|
||||||
<tr *ngFor="let server of p.servers; let ri = index;">
|
<tbody>
|
||||||
<td><a href="{{ server.url }}" title="{{ server.title }}">{{ server.url }}</a></td>
|
<tr>
|
||||||
<td><span class="has-text-grey">{{ server.weight }}</span></td>
|
<td>Server</td>
|
||||||
</tr>
|
<td>Weight</td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
<tr *ngFor="let server of p.servers">
|
||||||
|
<td class="table-cell-limited"><a href="{{ server.url }}" [title]="server.id">{{ server.url }}</a></td>
|
||||||
|
<td><span class="has-text-grey">{{ server.weight }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Details -->
|
<!-- Details -->
|
||||||
<div *ngIf="p.section === 'details'">
|
<div *ngIf="p.section === 'details'" class="section-container">
|
||||||
|
|
||||||
<div *ngIf="p.loadBalancer">
|
<div *ngIf="p.loadBalancer" class="section-line">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-3">
|
<div class="column is-3">
|
||||||
<h2>Load Balancer</h2>
|
<h2 class="section-line-header">Load Balancer</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
@ -424,9 +458,9 @@
|
||||||
|
|
||||||
<div *ngIf="p.maxConn">
|
<div *ngIf="p.maxConn">
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="columns">
|
<div class="columns section-line">
|
||||||
<div class="column is-3">
|
<div class="column is-3">
|
||||||
<h2>Max Connections</h2>
|
<h2 class="section-line-header">Max Connections</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
@ -449,9 +483,9 @@
|
||||||
|
|
||||||
<div *ngIf="p.circuitBreaker">
|
<div *ngIf="p.circuitBreaker">
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="columns">
|
<div class="columns section-line">
|
||||||
<div class="column is-3">
|
<div class="column is-3">
|
||||||
<h2>Circuit Breaker</h2>
|
<h2 class="section-line-header">Circuit Breaker</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
@ -468,9 +502,9 @@
|
||||||
|
|
||||||
<div *ngIf="p.healthCheck">
|
<div *ngIf="p.healthCheck">
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="columns">
|
<div class="columns section-line">
|
||||||
<div class="column is-3">
|
<div class="column is-3">
|
||||||
<h2>Health Check</h2>
|
<h2 class="section-line-header">Health Check</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
|
@ -505,81 +539,79 @@
|
||||||
|
|
||||||
<div *ngIf="p.buffering">
|
<div *ngIf="p.buffering">
|
||||||
<hr>
|
<hr>
|
||||||
<div class="columns list-title">
|
<div class="section-line">
|
||||||
<div class="column is-12">
|
<h2 class="section-line-header">Buffering</h2>
|
||||||
<h2>Buffering</h2>
|
<table class="table is-fullwidth is-hoverable table-fixedd">
|
||||||
</div>
|
<tbody>
|
||||||
</div>
|
<tr>
|
||||||
<div class="list-item">
|
<td><span class="has-text-grey">Request Body Bytes</span></td>
|
||||||
<div class="columns">
|
<td>
|
||||||
<div class="column is-4">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<span>Request Body Bytes</span>
|
<div class="control">
|
||||||
</div>
|
<div class="tags has-addons">
|
||||||
<div class="column is-4">
|
<span class="tag is-light">Max</span>
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<span class="tag is-info">{{ p.buffering.maxRequestBodyBytes }}</span>
|
||||||
<div class="control">
|
</div>
|
||||||
<div class="tags has-addons">
|
</div>
|
||||||
<span class="tag is-light">Max</span>
|
|
||||||
<span class="tag is-info">{{ p.buffering.maxRequestBodyBytes }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
<td>
|
||||||
</div>
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<div class="column is-4">
|
<div class="control">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="tags has-addons">
|
||||||
<div class="control">
|
<span class="tag is-light">Men</span>
|
||||||
<div class="tags has-addons">
|
<span class="tag is-info">{{ p.buffering.memRequestBodyBytes }}</span>
|
||||||
<span class="tag is-light">Men</span>
|
</div>
|
||||||
<span class="tag is-info">{{ p.buffering.memRequestBodyBytes }}</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
<tr>
|
||||||
</div>
|
<td><span class="has-text-grey">Response Body Bytes</span></td>
|
||||||
</div>
|
<td>
|
||||||
<div class="list-item">
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<div class="columns">
|
<div class="control">
|
||||||
<div class="column is-4">
|
<div class="tags has-addons">
|
||||||
<span>Response Body Bytes</span>
|
<span class="tag is-light">Max</span>
|
||||||
</div>
|
<span class="tag is-info">{{ p.buffering.maxResponseBodyBytes }}</span>
|
||||||
<div class="column is-4">
|
</div>
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
</div>
|
||||||
<div class="control">
|
|
||||||
<div class="tags has-addons">
|
|
||||||
<span class="tag is-light">Max</span>
|
|
||||||
<span class="tag is-info">{{ p.buffering.maxResponseBodyBytes }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
<td>
|
||||||
</div>
|
<div class="field is-grouped is-grouped-multiline">
|
||||||
<div class="column is-4">
|
<div class="control">
|
||||||
<div class="field is-grouped is-grouped-multiline">
|
<div class="tags has-addons">
|
||||||
<div class="control">
|
<span class="tag is-light">Men</span>
|
||||||
<div class="tags has-addons">
|
<span class="tag is-info">{{ p.buffering.memResponseBodyBytes }}</span>
|
||||||
<span class="tag is-light">Men</span>
|
</div>
|
||||||
<span class="tag is-info">{{ p.buffering.memResponseBodyBytes }}</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
<tr>
|
||||||
</div>
|
<td class="has-text-grey">Retry Expression</td>
|
||||||
</div>
|
<td colspan="2"><span class="tag is-info">{{ p.buffering.retryExpression }}</span></td>
|
||||||
<div class="list-item">
|
</tr>
|
||||||
<div class="columns">
|
</tbody>
|
||||||
<div class="column is-4">
|
</table>
|
||||||
<span>Retry Expression</span>
|
|
||||||
</div>
|
|
||||||
<div class="column is-8">
|
|
||||||
<span class="tag is-info">{{ p.buffering.retryExpression }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div *ngIf="backends.length > maxItem">
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header has-background-warning has-text-black">
|
||||||
|
Too many backends to display, please add a filter.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
import * as _ from 'lodash';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import * as _ from "lodash";
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-providers',
|
selector: 'app-providers',
|
||||||
|
@ -10,8 +10,9 @@ import * as _ from "lodash";
|
||||||
})
|
})
|
||||||
export class ProvidersComponent implements OnInit, OnDestroy {
|
export class ProvidersComponent implements OnInit, OnDestroy {
|
||||||
sub: Subscription;
|
sub: Subscription;
|
||||||
|
maxItem: number;
|
||||||
keys: string[];
|
keys: string[];
|
||||||
data: any;
|
previousKeys: string[];
|
||||||
previousData: any;
|
previousData: any;
|
||||||
providers: any;
|
providers: any;
|
||||||
tab: string;
|
tab: string;
|
||||||
|
@ -20,6 +21,7 @@ export class ProvidersComponent implements OnInit, OnDestroy {
|
||||||
constructor(private apiService: ApiService) { }
|
constructor(private apiService: ApiService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.maxItem = 100;
|
||||||
this.keyword = '';
|
this.keyword = '';
|
||||||
this.sub = Observable.timer(0, 2000)
|
this.sub = Observable.timer(0, 2000)
|
||||||
.timeInterval()
|
.timeInterval()
|
||||||
|
@ -27,28 +29,23 @@ export class ProvidersComponent implements OnInit, OnDestroy {
|
||||||
.subscribe(data => {
|
.subscribe(data => {
|
||||||
if (!_.isEqual(this.previousData, data)) {
|
if (!_.isEqual(this.previousData, data)) {
|
||||||
this.previousData = _.cloneDeep(data);
|
this.previousData = _.cloneDeep(data);
|
||||||
this.data = data;
|
|
||||||
this.providers = data;
|
this.providers = data;
|
||||||
this.keys = Object.keys(this.providers);
|
|
||||||
this.tab = this.keys[0];
|
const keys = Object.keys(this.providers);
|
||||||
|
if (!_.isEqual(this.previousKeys, keys)) {
|
||||||
|
this.keys = keys;
|
||||||
|
|
||||||
|
// keep current tab or set to the first tab
|
||||||
|
if (!this.tab || (this.tab && !this.keys.includes(this.tab))) {
|
||||||
|
this.tab = this.keys[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
filter(): void {
|
trackItem(tab): (index, item) => string {
|
||||||
const keyword = this.keyword.toLowerCase();
|
return (index, item): string => tab + '-' + item.id;
|
||||||
this.providers = Object.keys(this.data)
|
|
||||||
.filter(value => value !== 'acme' && value !== 'ACME')
|
|
||||||
.reduce((acc, curr) => {
|
|
||||||
return Object.assign(acc, {
|
|
||||||
[curr]: {
|
|
||||||
backends: this.data[curr].backends.filter(d => d.id.toLowerCase().includes(keyword)),
|
|
||||||
frontends: this.data[curr].frontends.filter(d => {
|
|
||||||
return d.id.toLowerCase().includes(keyword) || d.backend.toLowerCase().includes(keyword);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
|
21
webui/src/app/directives/let.directive.ts
Normal file
21
webui/src/app/directives/let.directive.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||||
|
|
||||||
|
interface LetContext<T> {
|
||||||
|
appLet: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[appLet]'
|
||||||
|
})
|
||||||
|
export class LetDirective<T> {
|
||||||
|
private _context: LetContext<T> = {appLet: null};
|
||||||
|
|
||||||
|
constructor(_viewContainer: ViewContainerRef, _templateRef: TemplateRef<LetContext<T>>) {
|
||||||
|
_viewContainer.createEmbeddedView(_templateRef, this._context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set appLet(value: T) {
|
||||||
|
this._context.appLet = value;
|
||||||
|
}
|
||||||
|
}
|
17
webui/src/app/pipes/backend.filter.pipe.ts
Normal file
17
webui/src/app/pipes/backend.filter.pipe.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'backendFilter',
|
||||||
|
pure: false
|
||||||
|
})
|
||||||
|
export class BackendFilterPipe implements PipeTransform {
|
||||||
|
transform(items: any[], filter: string): any {
|
||||||
|
if (!items || !filter) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = filter.toLowerCase();
|
||||||
|
return items.filter(d => d.id.toLowerCase().includes(keyword)
|
||||||
|
|| d.servers.some(r => r.url.toLowerCase().includes(keyword)));
|
||||||
|
}
|
||||||
|
}
|
18
webui/src/app/pipes/frontend.filter.pipe.ts
Normal file
18
webui/src/app/pipes/frontend.filter.pipe.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'frontendFilter',
|
||||||
|
pure: false
|
||||||
|
})
|
||||||
|
export class FrontendFilterPipe implements PipeTransform {
|
||||||
|
transform(items: any[], filter: string): any {
|
||||||
|
if (!items || !filter) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyword = filter.toLowerCase();
|
||||||
|
return items.filter(d => d.id.toLowerCase().includes(keyword)
|
||||||
|
|| d.backend.toLowerCase().includes(keyword)
|
||||||
|
|| d.routes.some(r => r.rule.toLowerCase().includes(keyword)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { PipeTransform, Pipe } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
@Pipe({ name: 'keys' })
|
@Pipe({name: 'keys'})
|
||||||
export class KeysPipe implements PipeTransform {
|
export class KeysPipe implements PipeTransform {
|
||||||
transform(value, args: string[]): any {
|
transform(value, args: string[]): any {
|
||||||
return Object.keys(value);
|
return Object.keys(value);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
import 'rxjs/add/observable/empty';
|
import 'rxjs/add/observable/empty';
|
||||||
import 'rxjs/add/observable/of';
|
import 'rxjs/add/observable/of';
|
||||||
import 'rxjs/add/operator/catch';
|
import 'rxjs/add/operator/catch';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
import 'rxjs/add/operator/retry';
|
import 'rxjs/add/operator/retry';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
export interface ProviderType {
|
export interface ProviderType {
|
||||||
[provider: string]: {
|
[provider: string]: {
|
||||||
|
@ -25,7 +25,7 @@ export class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchVersion(): Observable<any> {
|
fetchVersion(): Observable<any> {
|
||||||
return this.http.get(`/api/version`, { headers: this.headers })
|
return this.http.get('../api/version', {headers: this.headers})
|
||||||
.retry(4)
|
.retry(4)
|
||||||
.catch((err: HttpErrorResponse) => {
|
.catch((err: HttpErrorResponse) => {
|
||||||
console.error(`[version] returned code ${err.status}, body was: ${err.error}`);
|
console.error(`[version] returned code ${err.status}, body was: ${err.error}`);
|
||||||
|
@ -34,7 +34,7 @@ export class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchHealthStatus(): Observable<any> {
|
fetchHealthStatus(): Observable<any> {
|
||||||
return this.http.get(`/health`, { headers: this.headers })
|
return this.http.get('../health', {headers: this.headers})
|
||||||
.retry(2)
|
.retry(2)
|
||||||
.catch((err: HttpErrorResponse) => {
|
.catch((err: HttpErrorResponse) => {
|
||||||
console.error(`[health] returned code ${err.status}, body was: ${err.error}`);
|
console.error(`[health] returned code ${err.status}, body was: ${err.error}`);
|
||||||
|
@ -43,46 +43,53 @@ export class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchProviders(): Observable<any> {
|
fetchProviders(): Observable<any> {
|
||||||
return this.http.get(`/api/providers`, { headers: this.headers })
|
return this.http.get('../api/providers', {headers: this.headers})
|
||||||
.retry(2)
|
.retry(2)
|
||||||
.catch((err: HttpErrorResponse) => {
|
.catch((err: HttpErrorResponse) => {
|
||||||
console.error(`[providers] returned code ${err.status}, body was: ${err.error}`);
|
console.error(`[providers] returned code ${err.status}, body was: ${err.error}`);
|
||||||
return Observable.of<any>({});
|
return Observable.of<any>({});
|
||||||
})
|
})
|
||||||
.map(this.parseProviders);
|
.map((data: any): ProviderType => this.parseProviders(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
parseProviders(data: any): ProviderType {
|
parseProviders(data: any): ProviderType {
|
||||||
return Object.keys(data)
|
return Object.keys(data)
|
||||||
.filter(value => value !== 'acme' && value !== 'ACME')
|
.filter(value => value !== 'acme' && value !== 'ACME')
|
||||||
.reduce((acc, curr) => {
|
.reduce((acc, curr) => {
|
||||||
acc[curr] = {
|
acc[curr] = {};
|
||||||
backends: Object.keys(data[curr].backends || {}).map(key => {
|
|
||||||
data[curr].backends[key].id = key;
|
acc[curr].frontends = this.toArray(data[curr].frontends, 'id')
|
||||||
data[curr].backends[key].servers = Object.keys(data[curr].backends[key].servers || {}).map(server => {
|
.map(frontend => {
|
||||||
return {
|
frontend.routes = this.toArray(frontend.routes, 'id');
|
||||||
title: server,
|
frontend.errors = this.toArray(frontend.errors, 'id');
|
||||||
url: data[curr].backends[key].servers[server].url,
|
if (frontend.headers) {
|
||||||
weight: data[curr].backends[key].servers[server].weight
|
frontend.headers.customRequestHeaders = this.toHeaderArray(frontend.headers.customRequestHeaders);
|
||||||
};
|
frontend.headers.customResponseHeaders = this.toHeaderArray(frontend.headers.customResponseHeaders);
|
||||||
|
frontend.headers.sslProxyHeaders = this.toHeaderArray(frontend.headers.sslProxyHeaders);
|
||||||
|
}
|
||||||
|
return frontend;
|
||||||
});
|
});
|
||||||
|
|
||||||
return data[curr].backends[key];
|
acc[curr].backends = this.toArray(data[curr].backends, 'id')
|
||||||
}),
|
.map(backend => {
|
||||||
frontends: Object.keys(data[curr].frontends || {}).map(key => {
|
backend.servers = this.toArray(backend.servers, 'id');
|
||||||
data[curr].frontends[key].id = key;
|
return backend;
|
||||||
data[curr].frontends[key].routes = Object.keys(data[curr].frontends[key].routes || {}).map(route => {
|
|
||||||
return {
|
|
||||||
title: route,
|
|
||||||
rule: data[curr].frontends[key].routes[route].rule
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return data[curr].frontends[key];
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toHeaderArray(data: any): any[] {
|
||||||
|
return Object.keys(data || {}).map(key => ({name: key, value: data[key]}));
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(data: any, fieldKeyName: string): any[] {
|
||||||
|
return Object.keys(data || {}).map(key => {
|
||||||
|
data[key][fieldKeyName] = key;
|
||||||
|
return data[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
@charset "utf-8"
|
@charset "utf-8"
|
||||||
|
|
||||||
@import 'typography'
|
@import 'typography'
|
||||||
|
@import 'variables'
|
||||||
@import 'colors'
|
@import 'colors'
|
||||||
@import '../../node_modules/bulma/sass/utilities/all'
|
@import '~bulma/sass/utilities/all'
|
||||||
@import '../../node_modules/bulma/sass/base/all'
|
@import '~bulma/sass/base/all'
|
||||||
@import '../../node_modules/bulma/sass/grid/all'
|
@import '~bulma/sass/grid/all'
|
||||||
@import '../../node_modules/bulma/sass/elements/container'
|
@import '~bulma/sass/elements/container'
|
||||||
@import '../../node_modules/bulma/sass/elements/tag'
|
@import '~bulma/sass/elements/tag'
|
||||||
@import '../../node_modules/bulma/sass/elements/box'
|
@import '~bulma/sass/elements/other'
|
||||||
@import '../../node_modules/bulma/sass/elements/form'
|
@import '~bulma/sass/elements/box'
|
||||||
@import '../../node_modules/bulma/sass/elements/table'
|
@import '~bulma/sass/elements/form'
|
||||||
@import '../../node_modules/bulma/sass/components/navbar'
|
@import '~bulma/sass/elements/table'
|
||||||
@import '../../node_modules/bulma/sass/components/tabs'
|
@import '~bulma/sass/components/navbar'
|
||||||
@import '../../node_modules/bulma/sass/elements/notification'
|
@import '~bulma/sass/components/tabs'
|
||||||
|
@import '~bulma/sass/elements/notification'
|
||||||
@import 'nav'
|
@import 'nav'
|
||||||
@import 'content'
|
@import 'content'
|
||||||
@import 'message'
|
@import 'message'
|
||||||
@import 'label'
|
|
||||||
@import 'charts'
|
@import 'charts'
|
||||||
@import 'helper'
|
@import 'helper'
|
||||||
|
|
||||||
|
|
|
@ -30,12 +30,6 @@
|
||||||
height: 320px
|
height: 320px
|
||||||
background-color: $white
|
background-color: $white
|
||||||
|
|
||||||
.bar
|
|
||||||
fill: rgba($blue, 0.91)
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
fill: lighten($blue, 10)
|
|
||||||
|
|
||||||
.axis text
|
.axis text
|
||||||
fill: $text
|
fill: $text
|
||||||
font: 10px sans-serif
|
font: 10px sans-serif
|
||||||
|
|
|
@ -1,46 +1,21 @@
|
||||||
.content
|
.content
|
||||||
background: transparent
|
background: transparent
|
||||||
margin: 40px 0
|
margin: 2rem 0
|
||||||
|
|
||||||
.subtitle
|
.subtitle
|
||||||
font-size: 15px
|
|
||||||
text-transform: uppercase
|
|
||||||
color: $black
|
color: $black
|
||||||
|
font-size: 0.9rem
|
||||||
font-weight: $weight-bold
|
font-weight: $weight-bold
|
||||||
text-transform: uppercase
|
text-transform: uppercase
|
||||||
margin: 10px 0 0 0
|
|
||||||
|
|
||||||
.list-title
|
.subtitle-name
|
||||||
color: $text-dark
|
padding-left: 0.5rem
|
||||||
weight: $weight-semibold
|
|
||||||
margin: 5px 0 0 0
|
|
||||||
|
|
||||||
.list-item
|
|
||||||
width: 100%
|
|
||||||
display: block
|
|
||||||
align-items: center
|
|
||||||
font-size: 12px
|
|
||||||
padding: 6px 10px
|
|
||||||
border-top: 1px solid $border-light
|
|
||||||
|
|
||||||
.columns
|
|
||||||
|
|
||||||
.column
|
|
||||||
display: flex
|
|
||||||
align-items: center
|
|
||||||
|
|
||||||
.icon
|
|
||||||
width: 22px
|
|
||||||
height: 22px
|
|
||||||
display: block
|
|
||||||
float: left
|
|
||||||
margin-right: 10px
|
|
||||||
|
|
||||||
.content-item
|
.content-item
|
||||||
background: $white
|
background: $white
|
||||||
border: 1px solid $border-secondary
|
border: 1px solid $border-secondary
|
||||||
margin: 10px 0
|
margin: 10px 0
|
||||||
border-radius: 4px
|
border-radius: $traefik-border-radius
|
||||||
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
|
|
||||||
h2
|
h2
|
||||||
|
@ -82,7 +57,7 @@
|
||||||
|
|
||||||
img
|
img
|
||||||
width: 40px
|
width: 40px
|
||||||
heught: 40px
|
height: 40px
|
||||||
display: block
|
display: block
|
||||||
float: left
|
float: left
|
||||||
margin-right: 10px
|
margin-right: 10px
|
||||||
|
@ -106,37 +81,27 @@
|
||||||
margin: 15px auto
|
margin: 15px auto
|
||||||
|
|
||||||
.search-container
|
.search-container
|
||||||
height: 50px
|
|
||||||
background: $white
|
background: $white
|
||||||
border-radius: 4px
|
|
||||||
color: $black
|
color: $black
|
||||||
margin: 10px 0
|
|
||||||
display: flex
|
display: flex
|
||||||
align-items: center
|
align-items: center
|
||||||
position: relative
|
border-radius: $traefik-border-radius
|
||||||
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
border: 1px solid $border-secondary
|
border: 1px solid $border-secondary
|
||||||
|
position: relative
|
||||||
|
height: 3rem
|
||||||
|
|
||||||
.icon
|
.search-button
|
||||||
position: absolute
|
position: absolute
|
||||||
left: 10px
|
left: 1rem
|
||||||
top: 13px
|
top: 0.8rem
|
||||||
|
|
||||||
input
|
input
|
||||||
font-size: 16px
|
|
||||||
color: $text
|
color: $text
|
||||||
width: 100%
|
|
||||||
height: 48px
|
|
||||||
padding-left: 50px
|
|
||||||
border: none
|
border: none
|
||||||
|
border-radius: $traefik-border-radius
|
||||||
outline: none
|
outline: none
|
||||||
|
font-size: 1rem
|
||||||
font-weight: $weight-light
|
font-weight: $weight-light
|
||||||
border-radius: 4px
|
width: 100%
|
||||||
|
padding-left: 2.8rem
|
||||||
.notification
|
|
||||||
background: $white
|
|
||||||
border-radius: 4px
|
|
||||||
color: $text
|
|
||||||
font-size: 16px
|
|
||||||
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
|
||||||
border: 1px solid $border-secondary
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
.label
|
|
||||||
padding: 5px 10px
|
|
||||||
background: $white
|
|
||||||
color: $color
|
|
||||||
font-size: 12px
|
|
||||||
font-family: $weight-semibold
|
|
||||||
width: 100%
|
|
||||||
display: flex
|
|
||||||
align-items: center
|
|
||||||
justify-content: center
|
|
||||||
border: 1px solid $border
|
|
||||||
background: linear-gradient(0deg, #F2F4F7 0%, #FFFFFF 100%)
|
|
||||||
|
|
||||||
&.green
|
|
||||||
background: $green-secondary
|
|
||||||
|
|
||||||
&.red
|
|
||||||
background: $red-secondary
|
|
||||||
|
|
||||||
&.yellow
|
|
||||||
background: $yellow-secondary
|
|
||||||
|
|
||||||
&.blue
|
|
||||||
background: $blue-secondary
|
|
||||||
|
|
||||||
span
|
|
||||||
display: inline-flex
|
|
||||||
float: left
|
|
||||||
align-items: center
|
|
|
@ -1,89 +1,65 @@
|
||||||
.message
|
.message
|
||||||
display: block
|
display: block
|
||||||
font-size: 14px
|
font-size: 0.8rem
|
||||||
margin: 20px 0 30px 0
|
margin: 1rem 0 1.5rem 0
|
||||||
|
padding-bottom: 0.3rem
|
||||||
border: 1px solid $border
|
border: 1px solid $border
|
||||||
background: $white
|
background: $white
|
||||||
border-radius: 4px
|
border-radius: $traefik-border-radius
|
||||||
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
|
|
||||||
.message-header
|
.message-header
|
||||||
color: $color-secondary
|
color: $color-secondary
|
||||||
border-bottom: 1px solid $border-secondary
|
border-bottom: 1px solid $border-secondary
|
||||||
padding: 20px 10px
|
padding: 0.6rem
|
||||||
background: #f8f9fa
|
border-top-left-radius: $traefik-border-radius
|
||||||
border-top-left-radius: 4px
|
border-top-right-radius: $traefik-border-radius
|
||||||
border-top-right-radius: 4px
|
|
||||||
|
.icon
|
||||||
|
display: block
|
||||||
|
float: left
|
||||||
|
width: 1.4rem
|
||||||
|
height: 1.4rem
|
||||||
|
margin-right: 0.5rem
|
||||||
|
|
||||||
h2
|
h2
|
||||||
font-size: 14px
|
|
||||||
weight: $weight-bold
|
|
||||||
display: flex
|
display: flex
|
||||||
justify-content: space-between
|
|
||||||
|
|
||||||
&.red
|
|
||||||
background: rgba($red-secondary, 0.4)
|
|
||||||
border-bottom: 1px solid $red-secondary
|
|
||||||
color: $red-secondary
|
|
||||||
|
|
||||||
p
|
|
||||||
color: $red-secondary
|
|
||||||
|
|
||||||
&.green
|
|
||||||
background-color: rgba($green-secondary, 0.4)
|
|
||||||
border-bottom: 1px solid $green-secondary
|
|
||||||
color: $green-secondary
|
|
||||||
|
|
||||||
p
|
|
||||||
color: darken($green-secondary, 10) !important
|
|
||||||
|
|
||||||
&.orange
|
|
||||||
background-color: rgba($orange-secondary, 0.4)
|
|
||||||
border-bottom: 1px solid $orange-secondary
|
|
||||||
color: $orange-secondary
|
|
||||||
|
|
||||||
p
|
|
||||||
color: $orange-secondary
|
|
||||||
|
|
||||||
&.blue
|
|
||||||
background-color: rgba($blue-background, 0.4)
|
|
||||||
border-bottom: 1px solid $blue-background
|
|
||||||
color: $blue-background
|
|
||||||
|
|
||||||
p
|
|
||||||
color: $blue-background !important
|
|
||||||
|
|
||||||
img
|
img
|
||||||
margin-right: 15px
|
margin-right: 15px
|
||||||
|
|
||||||
.message-body
|
.message-body
|
||||||
|
|
||||||
.field
|
.tabs
|
||||||
margin: 5px 10px
|
margin-bottom: 0.5rem
|
||||||
padding-bottom: 10px
|
|
||||||
|
|
||||||
.tags-list
|
.section-container
|
||||||
margin: 5px 10px
|
padding: 0.3em 0 0 0
|
||||||
|
|
||||||
.control
|
.section-line
|
||||||
width: 100%
|
padding: 0 0.75em
|
||||||
margin: 5px 0
|
|
||||||
|
|
||||||
.tags
|
.section-line-header
|
||||||
width: 100%
|
padding: 0.2em 0 0 0
|
||||||
|
|
||||||
.tag
|
// required for small screen (without -> table overlapping)
|
||||||
width: 50%
|
.table-fixed
|
||||||
|
table-layout: fixed
|
||||||
|
|
||||||
|
// required for small screen (without -> table overlapping)
|
||||||
|
.table-fixed-break
|
||||||
|
table-layout: fixed
|
||||||
|
word-wrap: break-word
|
||||||
|
|
||||||
|
.table-cell-limited
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
|
||||||
|
.table-col-75
|
||||||
|
width: 75%
|
||||||
|
|
||||||
h2
|
h2
|
||||||
margin: 10px 10px 0 10px
|
|
||||||
color: $black
|
color: $black
|
||||||
|
|
||||||
hr
|
hr
|
||||||
margin: 5px 0
|
margin: 5px 0
|
||||||
|
|
||||||
.message-subheader
|
|
||||||
border-bottom: 1px solid $border-secondary
|
|
||||||
padding: 10px
|
|
||||||
margin-bottom: 5px
|
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
.navbar
|
.navbar
|
||||||
border-bottom: 1px solid $border
|
border-bottom: 1px solid $border
|
||||||
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
box-shadow: 1px 2px 5px rgba($border, 0.4)
|
||||||
height: 60px
|
|
||||||
|
|
||||||
.navbar-item
|
.navbar-item
|
||||||
font-size: 13px
|
font-size: 0.8rem
|
||||||
text-transform: uppercase
|
text-transform: uppercase
|
||||||
font-weight: $weight-semibold
|
font-weight: $weight-semibold
|
||||||
|
|
||||||
.navbar-logo
|
.navbar-logo
|
||||||
width: 40px
|
width: 40px
|
||||||
min-height: 40px
|
min-height: 40px
|
||||||
|
|
||||||
&:hover
|
|
||||||
background: transparent
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
=font-face($family, $path, $weight: normal, $style: normal)
|
=font-face($family, $path, $weight: normal, $style: normal)
|
||||||
@font-face
|
@font-face
|
||||||
font-family: $family
|
font-family: $family
|
||||||
src: url('#{$path}.ttf') format('truetype')
|
src: url('./#{$path}.ttf') format('truetype')
|
||||||
font-weight: $weight
|
font-weight: $weight
|
||||||
font-style: $style
|
font-style: $style
|
||||||
|
|
||||||
+font-face('Open Sans', '/assets/fonts/OpenSans-Light', 300, 'light')
|
+font-face('Open Sans', 'assets/fonts/OpenSans-Light', 300, 'light')
|
||||||
+font-face('Open Sans', '/assets/fonts/OpenSans-Regular', 400, 'regular')
|
+font-face('Open Sans', 'assets/fonts/OpenSans-Regular', 400, 'regular')
|
||||||
+font-face('Open Sans', '/assets/fonts/OpenSans-Semibold', 600, 'semibold')
|
+font-face('Open Sans', 'assets/fonts/OpenSans-Semibold', 600, 'semibold')
|
||||||
+font-face('Open Sans', '/assets/fonts/OpenSans-Bold', 700, 'bold')
|
+font-face('Open Sans', 'assets/fonts/OpenSans-Bold', 700, 'bold')
|
||||||
+font-face('Open Sans', '/assets/fonts/OpenSans-ExtraBold', 800, 'extrabold')
|
+font-face('Open Sans', 'assets/fonts/OpenSans-ExtraBold', 800, 'extrabold')
|
||||||
|
|
||||||
$open-sans: 'Open Sans', sans-serif
|
$open-sans: 'Open Sans', sans-serif
|
||||||
|
|
1
webui/src/styles/variables.sass
Normal file
1
webui/src/styles/variables.sass
Normal file
|
@ -0,0 +1 @@
|
||||||
|
$traefik-border-radius: 4px
|
|
@ -1031,9 +1031,9 @@ builtin-status-codes@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||||
|
|
||||||
bulma@^0.6.2:
|
bulma@^0.7.0:
|
||||||
version "0.6.2"
|
version "0.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.6.2.tgz#f4b1d11d5acc51a79644eb0a2b0b10649d3d71f5"
|
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.7.1.tgz#73c2e3b2930c90cc272029cbd19918b493fca486"
|
||||||
|
|
||||||
bytes@3.0.0:
|
bytes@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue