Merge branch 'v3.0' of github.com:traefik/traefik
This commit is contained in:
commit
e9fcdb9702
56 changed files with 3034 additions and 148 deletions
|
@ -1545,7 +1545,8 @@
|
||||||
"hide": 0,
|
"hide": 0,
|
||||||
"includeAll": false,
|
"includeAll": false,
|
||||||
"multi": false,
|
"multi": false,
|
||||||
"name": "datasource",
|
"name": "DS_PROMETHEUS",
|
||||||
|
"label": "datasource",
|
||||||
"options": [],
|
"options": [],
|
||||||
"query": "prometheus",
|
"query": "prometheus",
|
||||||
"refresh": 1,
|
"refresh": 1,
|
||||||
|
|
|
@ -1537,7 +1537,8 @@
|
||||||
"hide": 0,
|
"hide": 0,
|
||||||
"includeAll": false,
|
"includeAll": false,
|
||||||
"multi": false,
|
"multi": false,
|
||||||
"name": "datasource",
|
"name": "DS_PROMETHEUS",
|
||||||
|
"label": "datasource",
|
||||||
"options": [],
|
"options": [],
|
||||||
"query": "prometheus",
|
"query": "prometheus",
|
||||||
"refresh": 1,
|
"refresh": 1,
|
||||||
|
|
|
@ -11,7 +11,11 @@ Automatic HTTPS
|
||||||
You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation.
|
You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation.
|
||||||
|
|
||||||
!!! warning "Let's Encrypt and Rate Limiting"
|
!!! warning "Let's Encrypt and Rate Limiting"
|
||||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to __one week__, and can not be overridden.
|
||||||
|
|
||||||
|
When running Traefik in a container this file should be persisted across restarts.
|
||||||
|
If Traefik requests new certificates each time it starts up, a crash-looping container can quickly reach Let's Encrypt's ratelimits.
|
||||||
|
To configure where certificates are stored, please take a look at the [storage](#storage) configuration.
|
||||||
|
|
||||||
Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option
|
Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option
|
||||||
when experimenting to avoid hitting this limit too fast.
|
when experimenting to avoid hitting this limit too fast.
|
||||||
|
|
|
@ -47,6 +47,12 @@ traefik.tls.certs.notAfterTimestamp
|
||||||
{prefix}.tls.certs.notAfterTimestamp
|
{prefix}.tls.certs.notAfterTimestamp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_config_reloads_total
|
||||||
|
traefik_config_last_reload_success
|
||||||
|
traefik_tls_certs_not_after
|
||||||
|
```
|
||||||
|
|
||||||
## EntryPoint Metrics
|
## EntryPoint Metrics
|
||||||
|
|
||||||
| Metric | Type | [Labels](#labels) | Description |
|
| Metric | Type | [Labels](#labels) | Description |
|
||||||
|
@ -95,6 +101,15 @@ traefik.entrypoint.responses.bytes.total
|
||||||
{prefix}.entrypoint.responses.bytes.total
|
{prefix}.entrypoint.responses.bytes.total
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_entrypoint_requests_total
|
||||||
|
traefik_entrypoint_requests_tls_total
|
||||||
|
traefik_entrypoint_request_duration_seconds
|
||||||
|
traefik_entrypoint_open_connections
|
||||||
|
traefik_entrypoint_requests_bytes_total
|
||||||
|
traefik_entrypoint_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
## Router Metrics
|
## Router Metrics
|
||||||
|
|
||||||
| Metric | Type | [Labels](#labels) | Description |
|
| Metric | Type | [Labels](#labels) | Description |
|
||||||
|
@ -143,6 +158,15 @@ traefik.router.responses.bytes.total
|
||||||
{prefix}.router.responses.bytes.total
|
{prefix}.router.responses.bytes.total
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_router_requests_total
|
||||||
|
traefik_router_requests_tls_total
|
||||||
|
traefik_router_request_duration_seconds
|
||||||
|
traefik_router_open_connections
|
||||||
|
traefik_router_requests_bytes_total
|
||||||
|
traefik_router_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
## Service Metrics
|
## Service Metrics
|
||||||
|
|
||||||
| Metric | Type | Labels | Description |
|
| Metric | Type | Labels | Description |
|
||||||
|
@ -201,6 +225,17 @@ traefik.service.responses.bytes.total
|
||||||
{prefix}.service.responses.bytes.total
|
{prefix}.service.responses.bytes.total
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```opentelemetry tab="OpenTelemetry"
|
||||||
|
traefik_service_requests_total
|
||||||
|
traefik_service_requests_tls_total
|
||||||
|
traefik_service_request_duration_seconds
|
||||||
|
traefik_service_open_connections
|
||||||
|
traefik_service_retries_total
|
||||||
|
traefik_service_server_up
|
||||||
|
traefik_service_requests_bytes_total
|
||||||
|
traefik_service_responses_bytes_total
|
||||||
|
```
|
||||||
|
|
||||||
## Labels
|
## Labels
|
||||||
|
|
||||||
Here is a comprehensive list of labels that are provided by the metrics:
|
Here is a comprehensive list of labels that are provided by the metrics:
|
||||||
|
|
|
@ -12,7 +12,7 @@ The tracing system allows developers to visualize call flows in their infrastruc
|
||||||
|
|
||||||
Traefik uses OpenTracing, an open standard designed for distributed tracing.
|
Traefik uses OpenTracing, an open standard designed for distributed tracing.
|
||||||
|
|
||||||
Traefik supports six tracing backends:
|
Traefik supports seven tracing backends:
|
||||||
|
|
||||||
- [Jaeger](./jaeger.md)
|
- [Jaeger](./jaeger.md)
|
||||||
- [Zipkin](./zipkin.md)
|
- [Zipkin](./zipkin.md)
|
||||||
|
@ -20,6 +20,7 @@ Traefik supports six tracing backends:
|
||||||
- [Instana](./instana.md)
|
- [Instana](./instana.md)
|
||||||
- [Haystack](./haystack.md)
|
- [Haystack](./haystack.md)
|
||||||
- [Elastic](./elastic.md)
|
- [Elastic](./elastic.md)
|
||||||
|
- [OpenTelemetry](./opentelemetry.md)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
||||||
## Routing Configuration
|
## Routing Configuration
|
||||||
|
|
||||||
When using Docker as a [provider](./overview.md),
|
When using Docker as a [provider](./overview.md),
|
||||||
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#-set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
||||||
|
|
||||||
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
||||||
|
|
||||||
|
|
|
@ -888,14 +888,20 @@ TLS certificates can be managed in Secrets objects.
|
||||||
|
|
||||||
### Communication Between Traefik and Pods
|
### Communication Between Traefik and Pods
|
||||||
|
|
||||||
|
!!! info "It is not possible to route requests directly to [Kubernetes services](https://kubernetes.io/docs/concepts/services-networking/service/ "Link to Kubernetes service docs")"
|
||||||
|
|
||||||
|
You can use an `ExternalName` service to forward requests to the Kubernetes service through DNS.
|
||||||
|
|
||||||
|
For doing so, you have to [allow external name services](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#allowexternalnameservices "Link to docs about allowing external name services").
|
||||||
|
|
||||||
Traefik automatically requests endpoint information based on the service provided in the ingress spec.
|
Traefik automatically requests endpoint information based on the service provided in the ingress spec.
|
||||||
Although Traefik will connect directly to the endpoints (pods),
|
Although Traefik will connect directly to the endpoints (pods),
|
||||||
it still checks the service port to see if TLS communication is required.
|
it still checks the service port to see if TLS communication is required.
|
||||||
|
|
||||||
There are 3 ways to configure Traefik to use https to communicate with pods:
|
There are 3 ways to configure Traefik to use HTTPS to communicate with pods:
|
||||||
|
|
||||||
1. If the service port defined in the ingress spec is `443` (note that you can still use `targetPort` to use a different port on your pod).
|
1. If the service port defined in the ingress spec is `443` (note that you can still use `targetPort` to use a different port on your pod).
|
||||||
1. If the service port defined in the ingress spec has a name that starts with https (such as `https-api`, `https-web` or just `https`).
|
1. If the service port defined in the ingress spec has a name that starts with `https` (such as `https-api`, `https-web` or just `https`).
|
||||||
1. If the service spec includes the annotation `traefik.ingress.kubernetes.io/service.serversscheme: https`.
|
1. If the service spec includes the annotation `traefik.ingress.kubernetes.io/service.serversscheme: https`.
|
||||||
|
|
||||||
If either of those configuration options exist, then the backend communication protocol is assumed to be TLS,
|
If either of those configuration options exist, then the backend communication protocol is assumed to be TLS,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ func (s *ErrorPagesSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct {
|
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct {
|
||||||
Server1 string
|
Server1 string
|
||||||
Server2 string
|
Server2 string
|
||||||
}{s.BackendIP, s.ErrorPageIP})
|
}{"http://" + s.BackendIP + ":80", s.ErrorPageIP})
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
@ -67,3 +68,33 @@ func (s *ErrorPagesSuite) TestErrorPage(c *check.C) {
|
||||||
err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred."))
|
err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred."))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ErrorPagesSuite) TestErrorPageFlush(c *check.C) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
rw.Header().Add("Transfer-Encoding", "chunked")
|
||||||
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
_, _ = rw.Write([]byte("KO"))
|
||||||
|
}))
|
||||||
|
|
||||||
|
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct {
|
||||||
|
Server1 string
|
||||||
|
Server2 string
|
||||||
|
}{srv.URL, s.ErrorPageIP})
|
||||||
|
defer os.Remove(file)
|
||||||
|
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
defer display(c)
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer s.killCmd(cmd)
|
||||||
|
|
||||||
|
frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
frontendReq.Host = "test.local"
|
||||||
|
|
||||||
|
err = try.Request(frontendReq, 2*time.Second,
|
||||||
|
try.BodyContains("An error occurred."),
|
||||||
|
try.HasHeaderValue("Content-Type", "text/html", true),
|
||||||
|
)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
[http.services.service1.loadBalancer]
|
[http.services.service1.loadBalancer]
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
[[http.services.service1.loadBalancer.servers]]
|
[[http.services.service1.loadBalancer.servers]]
|
||||||
url = "http://{{.Server1}}:80"
|
url = "{{.Server1}}"
|
||||||
|
|
||||||
[http.services.error.loadBalancer]
|
[http.services.error.loadBalancer]
|
||||||
[[http.services.error.loadBalancer.servers]]
|
[[http.services.error.loadBalancer.servers]]
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"priority": 18,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
|
6
integration/testdata/rawdata-crd.json
vendored
6
integration/testdata/rawdata-crd.json
vendored
|
@ -6,6 +6,7 @@
|
||||||
],
|
],
|
||||||
"service": "api@internal",
|
"service": "api@internal",
|
||||||
"rule": "PathPrefix(`/api`)",
|
"rule": "PathPrefix(`/api`)",
|
||||||
|
"priority": 18,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-test2-route-23c7f4c450289ee29016",
|
"service": "default-test2-route-23c7f4c450289ee29016",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)",
|
||||||
|
"priority": 46,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -46,6 +48,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-wrr1",
|
"service": "default-wrr1",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/wrr1`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/wrr1`)",
|
||||||
|
"priority": 38,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -57,6 +60,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-testst-route-60ad45fcb5fc1f5f3629",
|
"service": "default-testst-route-60ad45fcb5fc1f5f3629",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/serverstransport`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/serverstransport`)",
|
||||||
|
"priority": 50,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -68,6 +72,7 @@
|
||||||
],
|
],
|
||||||
"service": "other-ns-wrr3",
|
"service": "other-ns-wrr3",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/c`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/c`)",
|
||||||
|
"priority": 35,
|
||||||
"error": [
|
"error": [
|
||||||
"the service \"other-ns-wrr3@kubernetescrd\" does not exist"
|
"the service \"other-ns-wrr3@kubernetescrd\" does not exist"
|
||||||
],
|
],
|
||||||
|
@ -261,6 +266,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-test3.route-673acf455cb2dab0b43a",
|
"service": "default-test3.route-673acf455cb2dab0b43a",
|
||||||
"rule": "HostSNI(`*`)",
|
"rule": "HostSNI(`*`)",
|
||||||
|
"priority": -1,
|
||||||
"tls": {
|
"tls": {
|
||||||
"passthrough": false,
|
"passthrough": false,
|
||||||
"options": "default-mytlsoption"
|
"options": "default-mytlsoption"
|
||||||
|
|
5
integration/testdata/rawdata-gateway.json
vendored
5
integration/testdata/rawdata-gateway.json
vendored
|
@ -34,6 +34,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
"service": "default-http-app-1-my-gateway-web-1c0cf64bde37d9d0df06-wrr",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||||
|
"priority": 31,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr",
|
"service": "default-http-app-1-my-https-gateway-websecure-1c0cf64bde37d9d0df06-wrr",
|
||||||
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
|
||||||
|
"priority": 31,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
|
@ -150,6 +152,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0",
|
"service": "default-tcp-app-1-my-tcp-gateway-footcp-e3b0c44298fc1c149afb-wrr-0",
|
||||||
"rule": "HostSNI(`*`)",
|
"rule": "HostSNI(`*`)",
|
||||||
|
"priority": -1,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"footcp"
|
"footcp"
|
||||||
|
@ -161,6 +164,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0",
|
"service": "default-tcp-app-1-my-tls-gateway-footlsterminate-e3b0c44298fc1c149afb-wrr-0",
|
||||||
"rule": "HostSNI(`*`)",
|
"rule": "HostSNI(`*`)",
|
||||||
|
"priority": -1,
|
||||||
"tls": {
|
"tls": {
|
||||||
"passthrough": false
|
"passthrough": false
|
||||||
},
|
},
|
||||||
|
@ -175,6 +179,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0",
|
"service": "default-tls-app-1-my-tls-gateway-footlspassthrough-2279fe75c5156dc5eb26-wrr-0",
|
||||||
"rule": "HostSNI(`foo.bar`)",
|
"rule": "HostSNI(`foo.bar`)",
|
||||||
|
"priority": 18,
|
||||||
"tls": {
|
"tls": {
|
||||||
"passthrough": true
|
"passthrough": true
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-whoami-http",
|
"service": "default-whoami-http",
|
||||||
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
|
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
|
||||||
|
"priority": 44,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
|
4
integration/testdata/rawdata-ingress.json
vendored
4
integration/testdata/rawdata-ingress.json
vendored
|
@ -34,6 +34,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-whoami-http",
|
"service": "default-whoami-http",
|
||||||
"rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)",
|
"rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)",
|
||||||
|
"priority": 50,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-whoami-http",
|
"service": "default-whoami-http",
|
||||||
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
|
"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
|
||||||
|
"priority": 44,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -56,6 +58,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-whoami-80",
|
"service": "default-whoami-80",
|
||||||
"rule": "Host(`whoami.test.drop`) \u0026\u0026 PathPrefix(`/drop`)",
|
"rule": "Host(`whoami.test.drop`) \u0026\u0026 PathPrefix(`/drop`)",
|
||||||
|
"priority": 47,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
@ -67,6 +70,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-whoami-80",
|
"service": "default-whoami-80",
|
||||||
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
|
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
|
||||||
|
"priority": 47,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
],
|
],
|
||||||
"service": "default-whoami-80",
|
"service": "default-whoami-80",
|
||||||
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
|
"rule": "Host(`whoami.test.keep`) \u0026\u0026 PathPrefix(`/keep`)",
|
||||||
|
"priority": 47,
|
||||||
"status": "enabled",
|
"status": "enabled",
|
||||||
"using": [
|
"using": [
|
||||||
"web"
|
"web"
|
||||||
|
|
|
@ -24,6 +24,8 @@ type pageInfo struct {
|
||||||
type searchCriterion struct {
|
type searchCriterion struct {
|
||||||
Search string `url:"search"`
|
Search string `url:"search"`
|
||||||
Status string `url:"status"`
|
Status string `url:"status"`
|
||||||
|
ServiceName string `url:"serviceName"`
|
||||||
|
MiddlewareName string `url:"middlewareName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSearchCriterion(query url.Values) *searchCriterion {
|
func newSearchCriterion(query url.Values) *searchCriterion {
|
||||||
|
@ -33,12 +35,19 @@ func newSearchCriterion(query url.Values) *searchCriterion {
|
||||||
|
|
||||||
search := query.Get("search")
|
search := query.Get("search")
|
||||||
status := query.Get("status")
|
status := query.Get("status")
|
||||||
|
serviceName := query.Get("serviceName")
|
||||||
|
middlewareName := query.Get("middlewareName")
|
||||||
|
|
||||||
if status == "" && search == "" {
|
if status == "" && search == "" && serviceName == "" && middlewareName == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &searchCriterion{Search: search, Status: status}
|
return &searchCriterion{
|
||||||
|
Search: search,
|
||||||
|
Status: status,
|
||||||
|
ServiceName: serviceName,
|
||||||
|
MiddlewareName: middlewareName,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *searchCriterion) withStatus(name string) bool {
|
func (c *searchCriterion) withStatus(name string) bool {
|
||||||
|
@ -59,6 +68,34 @@ func (c *searchCriterion) searchIn(values ...string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *searchCriterion) filterService(name string) bool {
|
||||||
|
if c.ServiceName == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(name, "@") {
|
||||||
|
return c.ServiceName == name
|
||||||
|
}
|
||||||
|
|
||||||
|
before, _, _ := strings.Cut(c.ServiceName, "@")
|
||||||
|
|
||||||
|
return before == name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *searchCriterion) filterMiddleware(mns []string) bool {
|
||||||
|
if c.MiddlewareName == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mn := range mns {
|
||||||
|
if c.MiddlewareName == mn {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func pagination(request *http.Request, max int) (pageInfo, error) {
|
func pagination(request *http.Request, max int) (pageInfo, error) {
|
||||||
perPage, err := getIntParam(request, "per_page", defaultPerPage)
|
perPage, err := getIntParam(request, "per_page", defaultPerPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -69,7 +68,8 @@ func newMiddlewareRepresentation(name string, mi *runtime.MiddlewareInfo) middle
|
||||||
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
|
results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, rt := range h.runtimeConfiguration.Routers {
|
for name, rt := range h.runtimeConfiguration.Routers {
|
||||||
if keepRouter(name, rt, criterion) {
|
if keepRouter(name, rt, criterion) {
|
||||||
|
@ -77,9 +77,7 @@ func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortRouters(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -121,7 +119,8 @@ func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
|
||||||
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
|
results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, si := range h.runtimeConfiguration.Services {
|
for name, si := range h.runtimeConfiguration.Services {
|
||||||
if keepService(name, si, criterion) {
|
if keepService(name, si, criterion) {
|
||||||
|
@ -129,9 +128,7 @@ func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortServices(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -173,7 +170,8 @@ func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
|
||||||
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, mi := range h.runtimeConfiguration.Middlewares {
|
for name, mi := range h.runtimeConfiguration.Middlewares {
|
||||||
if keepMiddleware(name, mi, criterion) {
|
if keepMiddleware(name, mi, criterion) {
|
||||||
|
@ -181,9 +179,7 @@ func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortMiddlewares(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -227,7 +223,10 @@ func keepRouter(name string, item *runtime.RouterInfo, criterion *searchCriterio
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return criterion.withStatus(item.Status) && criterion.searchIn(item.Rule, name)
|
return criterion.withStatus(item.Status) &&
|
||||||
|
criterion.searchIn(item.Rule, name) &&
|
||||||
|
criterion.filterService(item.Service) &&
|
||||||
|
criterion.filterMiddleware(item.Middlewares)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keepService(name string, item *runtime.ServiceInfo, criterion *searchCriterion) bool {
|
func keepService(name string, item *runtime.ServiceInfo, criterion *searchCriterion) bool {
|
||||||
|
|
|
@ -202,6 +202,84 @@ func TestHandler_HTTP(t *testing.T) {
|
||||||
jsonFile: "testdata/routers-filtered-search.json",
|
jsonFile: "testdata/routers-filtered-search.json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "routers filtered by service",
|
||||||
|
path: "/api/http/routers?serviceName=fii-service@myprovider",
|
||||||
|
conf: runtime.Configuration{
|
||||||
|
Routers: map[string]*runtime.RouterInfo{
|
||||||
|
"test@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "fii-service@myprovider",
|
||||||
|
Rule: "Host(`fii.bar.other`)",
|
||||||
|
Middlewares: []string{"addPrefixTest", "auth"},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
"foo@otherprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "fii-service",
|
||||||
|
Rule: "Host(`fii.foo.other`)",
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
"bar@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusDisabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/routers-filtered-serviceName.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "routers filtered by middleware",
|
||||||
|
path: "/api/http/routers?middlewareName=auth",
|
||||||
|
conf: runtime.Configuration{
|
||||||
|
Routers: map[string]*runtime.RouterInfo{
|
||||||
|
"test@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "fii-service@myprovider",
|
||||||
|
Rule: "Host(`fii.bar.other`)",
|
||||||
|
Middlewares: []string{"addPrefixTest", "auth"},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
"foo@otherprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "fii-service",
|
||||||
|
Rule: "Host(`fii.foo.other`)",
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
"bar@myprovider": {
|
||||||
|
Router: &dynamic.Router{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusDisabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/routers-filtered-middlewareName.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "one router by id",
|
desc: "one router by id",
|
||||||
path: "/api/http/routers/bar@myprovider",
|
path: "/api/http/routers/bar@myprovider",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -62,7 +61,8 @@ func newTCPMiddlewareRepresentation(name string, mi *runtime.TCPMiddlewareInfo)
|
||||||
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
|
results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, rt := range h.runtimeConfiguration.TCPRouters {
|
for name, rt := range h.runtimeConfiguration.TCPRouters {
|
||||||
if keepTCPRouter(name, rt, criterion) {
|
if keepTCPRouter(name, rt, criterion) {
|
||||||
|
@ -70,9 +70,7 @@ func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortRouters(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -114,7 +112,8 @@ func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
|
||||||
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
|
results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, si := range h.runtimeConfiguration.TCPServices {
|
for name, si := range h.runtimeConfiguration.TCPServices {
|
||||||
if keepTCPService(name, si, criterion) {
|
if keepTCPService(name, si, criterion) {
|
||||||
|
@ -122,9 +121,7 @@ func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortServices(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -166,7 +163,8 @@ func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
|
||||||
func (h Handler) getTCPMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getTCPMiddlewares(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]tcpMiddlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
results := make([]tcpMiddlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, mi := range h.runtimeConfiguration.TCPMiddlewares {
|
for name, mi := range h.runtimeConfiguration.TCPMiddlewares {
|
||||||
if keepTCPMiddleware(name, mi, criterion) {
|
if keepTCPMiddleware(name, mi, criterion) {
|
||||||
|
@ -174,9 +172,7 @@ func (h Handler) getTCPMiddlewares(rw http.ResponseWriter, request *http.Request
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortMiddlewares(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -220,7 +216,10 @@ func keepTCPRouter(name string, item *runtime.TCPRouterInfo, criterion *searchCr
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return criterion.withStatus(item.Status) && criterion.searchIn(item.Rule, name)
|
return criterion.withStatus(item.Status) &&
|
||||||
|
criterion.searchIn(item.Rule, name) &&
|
||||||
|
criterion.filterService(item.Service) &&
|
||||||
|
criterion.filterMiddleware(item.Middlewares)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keepTCPService(name string, item *runtime.TCPServiceInfo, criterion *searchCriterion) bool {
|
func keepTCPService(name string, item *runtime.TCPServiceInfo, criterion *searchCriterion) bool {
|
||||||
|
|
|
@ -193,6 +193,89 @@ func TestHandler_TCP(t *testing.T) {
|
||||||
jsonFile: "testdata/tcprouters-filtered-search.json",
|
jsonFile: "testdata/tcprouters-filtered-search.json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "TCP routers filtered by service",
|
||||||
|
path: "/api/tcp/routers?serviceName=foo-service@myprovider",
|
||||||
|
conf: runtime.Configuration{
|
||||||
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
||||||
|
"test@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar.other`)",
|
||||||
|
TLS: &dynamic.RouterTCPTLSConfig{
|
||||||
|
Passthrough: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
},
|
||||||
|
Status: runtime.StatusWarning,
|
||||||
|
},
|
||||||
|
"foo@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "bar-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
},
|
||||||
|
Status: runtime.StatusDisabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/tcprouters-filtered-serviceName.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "TCP routers filtered by middleware",
|
||||||
|
path: "/api/tcp/routers?middlewareName=auth",
|
||||||
|
conf: runtime.Configuration{
|
||||||
|
TCPRouters: map[string]*runtime.TCPRouterInfo{
|
||||||
|
"test@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar.other`)",
|
||||||
|
Middlewares: []string{"inflightconn@myprovider"},
|
||||||
|
TLS: &dynamic.RouterTCPTLSConfig{
|
||||||
|
Passthrough: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
"bar@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"auth", "inflightconn@myprovider"},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusWarning,
|
||||||
|
},
|
||||||
|
"foo@myprovider": {
|
||||||
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "bar-service@myprovider",
|
||||||
|
Rule: "Host(`foo.bar`)",
|
||||||
|
Middlewares: []string{"inflightconn@myprovider", "auth"},
|
||||||
|
},
|
||||||
|
Status: runtime.StatusDisabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/tcprouters-filtered-middlewareName.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "one TCP router by id",
|
desc: "one TCP router by id",
|
||||||
path: "/api/tcp/routers/bar@myprovider",
|
path: "/api/tcp/routers/bar@myprovider",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -46,7 +45,8 @@ func newUDPServiceRepresentation(name string, si *runtime.UDPServiceInfo) udpSer
|
||||||
func (h Handler) getUDPRouters(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getUDPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]udpRouterRepresentation, 0, len(h.runtimeConfiguration.UDPRouters))
|
results := make([]udpRouterRepresentation, 0, len(h.runtimeConfiguration.UDPRouters))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, rt := range h.runtimeConfiguration.UDPRouters {
|
for name, rt := range h.runtimeConfiguration.UDPRouters {
|
||||||
if keepUDPRouter(name, rt, criterion) {
|
if keepUDPRouter(name, rt, criterion) {
|
||||||
|
@ -54,9 +54,7 @@ func (h Handler) getUDPRouters(rw http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortRouters(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -98,7 +96,8 @@ func (h Handler) getUDPRouter(rw http.ResponseWriter, request *http.Request) {
|
||||||
func (h Handler) getUDPServices(rw http.ResponseWriter, request *http.Request) {
|
func (h Handler) getUDPServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
results := make([]udpServiceRepresentation, 0, len(h.runtimeConfiguration.UDPServices))
|
results := make([]udpServiceRepresentation, 0, len(h.runtimeConfiguration.UDPServices))
|
||||||
|
|
||||||
criterion := newSearchCriterion(request.URL.Query())
|
query := request.URL.Query()
|
||||||
|
criterion := newSearchCriterion(query)
|
||||||
|
|
||||||
for name, si := range h.runtimeConfiguration.UDPServices {
|
for name, si := range h.runtimeConfiguration.UDPServices {
|
||||||
if keepUDPService(name, si, criterion) {
|
if keepUDPService(name, si, criterion) {
|
||||||
|
@ -106,9 +105,7 @@ func (h Handler) getUDPServices(rw http.ResponseWriter, request *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(results, func(i, j int) bool {
|
sortServices(query, results)
|
||||||
return results[i].Name < results[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
@ -152,7 +149,9 @@ func keepUDPRouter(name string, item *runtime.UDPRouterInfo, criterion *searchCr
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return criterion.withStatus(item.Status) && criterion.searchIn(name)
|
return criterion.withStatus(item.Status) &&
|
||||||
|
criterion.searchIn(name) &&
|
||||||
|
criterion.filterService(item.Service)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keepUDPService(name string, item *runtime.UDPServiceInfo, criterion *searchCriterion) bool {
|
func keepUDPService(name string, item *runtime.UDPServiceInfo, criterion *searchCriterion) bool {
|
||||||
|
|
|
@ -172,6 +172,40 @@ func TestHandler_UDP(t *testing.T) {
|
||||||
jsonFile: "testdata/udprouters-filtered-search.json",
|
jsonFile: "testdata/udprouters-filtered-search.json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "UDP routers filtered by service",
|
||||||
|
path: "/api/udp/routers?serviceName=foo-service@myprovider",
|
||||||
|
conf: runtime.Configuration{
|
||||||
|
UDPRouters: map[string]*runtime.UDPRouterInfo{
|
||||||
|
"test@myprovider": {
|
||||||
|
UDPRouter: &dynamic.UDPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service@myprovider",
|
||||||
|
},
|
||||||
|
Status: runtime.StatusEnabled,
|
||||||
|
},
|
||||||
|
"bar@myprovider": {
|
||||||
|
UDPRouter: &dynamic.UDPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "foo-service",
|
||||||
|
},
|
||||||
|
Status: runtime.StatusWarning,
|
||||||
|
},
|
||||||
|
"foo@myprovider": {
|
||||||
|
UDPRouter: &dynamic.UDPRouter{
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "bar-service@myprovider",
|
||||||
|
},
|
||||||
|
Status: runtime.StatusDisabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
nextPage: "1",
|
||||||
|
jsonFile: "testdata/udprouters-filtered-serviceName.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "one UDP router by id",
|
desc: "one UDP router by id",
|
||||||
path: "/api/udp/routers/bar@myprovider",
|
path: "/api/udp/routers/bar@myprovider",
|
||||||
|
|
386
pkg/api/sort.go
Normal file
386
pkg/api/sort.go
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sortByParam = "sortBy"
|
||||||
|
directionParam = "direction"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ascendantSorting = "asc"
|
||||||
|
descendantSorting = "desc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type orderedWithName interface {
|
||||||
|
name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type orderedRouter interface {
|
||||||
|
orderedWithName
|
||||||
|
|
||||||
|
provider() string
|
||||||
|
priority() int
|
||||||
|
status() string
|
||||||
|
rule() string
|
||||||
|
service() string
|
||||||
|
entryPointsCount() int
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortRouters[T orderedRouter](values url.Values, routers []T) {
|
||||||
|
sortBy := values.Get(sortByParam)
|
||||||
|
|
||||||
|
direction := values.Get(directionParam)
|
||||||
|
if direction == "" {
|
||||||
|
direction = ascendantSorting
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sortBy {
|
||||||
|
case "name":
|
||||||
|
sortByName(direction, routers)
|
||||||
|
|
||||||
|
case "provider":
|
||||||
|
sortByFunc(direction, routers, func(i int) string { return routers[i].provider() })
|
||||||
|
|
||||||
|
case "priority":
|
||||||
|
sortByFunc(direction, routers, func(i int) int { return routers[i].priority() })
|
||||||
|
|
||||||
|
case "status":
|
||||||
|
sortByFunc(direction, routers, func(i int) string { return routers[i].status() })
|
||||||
|
|
||||||
|
case "rule":
|
||||||
|
sortByFunc(direction, routers, func(i int) string { return routers[i].rule() })
|
||||||
|
|
||||||
|
case "service":
|
||||||
|
sortByFunc(direction, routers, func(i int) string { return routers[i].service() })
|
||||||
|
|
||||||
|
case "entryPoints":
|
||||||
|
sortByFunc(direction, routers, func(i int) int { return routers[i].entryPointsCount() })
|
||||||
|
|
||||||
|
default:
|
||||||
|
sortByName(direction, routers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r routerRepresentation) name() string {
|
||||||
|
return r.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r routerRepresentation) provider() string {
|
||||||
|
return r.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r routerRepresentation) priority() int {
|
||||||
|
return r.Priority
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r routerRepresentation) status() string {
|
||||||
|
return r.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r routerRepresentation) rule() string {
|
||||||
|
return r.Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r routerRepresentation) service() string {
|
||||||
|
return r.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r routerRepresentation) entryPointsCount() int {
|
||||||
|
return len(r.EntryPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r tcpRouterRepresentation) name() string {
|
||||||
|
return r.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r tcpRouterRepresentation) provider() string {
|
||||||
|
return r.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r tcpRouterRepresentation) priority() int {
|
||||||
|
return r.Priority
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r tcpRouterRepresentation) status() string {
|
||||||
|
return r.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r tcpRouterRepresentation) rule() string {
|
||||||
|
return r.Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r tcpRouterRepresentation) service() string {
|
||||||
|
return r.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r tcpRouterRepresentation) entryPointsCount() int {
|
||||||
|
return len(r.EntryPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r udpRouterRepresentation) name() string {
|
||||||
|
return r.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r udpRouterRepresentation) provider() string {
|
||||||
|
return r.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r udpRouterRepresentation) priority() int {
|
||||||
|
// noop
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r udpRouterRepresentation) status() string {
|
||||||
|
return r.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r udpRouterRepresentation) rule() string {
|
||||||
|
// noop
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r udpRouterRepresentation) service() string {
|
||||||
|
return r.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r udpRouterRepresentation) entryPointsCount() int {
|
||||||
|
return len(r.EntryPoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
type orderedService interface {
|
||||||
|
orderedWithName
|
||||||
|
|
||||||
|
resourceType() string
|
||||||
|
serversCount() int
|
||||||
|
provider() string
|
||||||
|
status() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortServices[T orderedService](values url.Values, services []T) {
|
||||||
|
sortBy := values.Get(sortByParam)
|
||||||
|
|
||||||
|
direction := values.Get(directionParam)
|
||||||
|
if direction == "" {
|
||||||
|
direction = ascendantSorting
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sortBy {
|
||||||
|
case "name":
|
||||||
|
sortByName(direction, services)
|
||||||
|
|
||||||
|
case "type":
|
||||||
|
sortByFunc(direction, services, func(i int) string { return services[i].resourceType() })
|
||||||
|
|
||||||
|
case "servers":
|
||||||
|
sortByFunc(direction, services, func(i int) int { return services[i].serversCount() })
|
||||||
|
|
||||||
|
case "provider":
|
||||||
|
sortByFunc(direction, services, func(i int) string { return services[i].provider() })
|
||||||
|
|
||||||
|
case "status":
|
||||||
|
sortByFunc(direction, services, func(i int) string { return services[i].status() })
|
||||||
|
|
||||||
|
default:
|
||||||
|
sortByName(direction, services)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceRepresentation) name() string {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceRepresentation) resourceType() string {
|
||||||
|
return s.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceRepresentation) serversCount() int {
|
||||||
|
// TODO: maybe disable that data point altogether,
|
||||||
|
// if we can't/won't compute a fully correct (recursive) result.
|
||||||
|
// Or "redefine" it as only the top-level count?
|
||||||
|
// Note: The current algo is equivalent to the webui one.
|
||||||
|
if s.LoadBalancer == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(s.LoadBalancer.Servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceRepresentation) provider() string {
|
||||||
|
return s.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceRepresentation) status() string {
|
||||||
|
return s.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s tcpServiceRepresentation) name() string {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s tcpServiceRepresentation) resourceType() string {
|
||||||
|
return s.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s tcpServiceRepresentation) serversCount() int {
|
||||||
|
// TODO: maybe disable that data point altogether,
|
||||||
|
// if we can't/won't compute a fully correct (recursive) result.
|
||||||
|
// Or "redefine" it as only the top-level count?
|
||||||
|
// Note: The current algo is equivalent to the webui one.
|
||||||
|
if s.LoadBalancer == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(s.LoadBalancer.Servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s tcpServiceRepresentation) provider() string {
|
||||||
|
return s.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s tcpServiceRepresentation) status() string {
|
||||||
|
return s.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s udpServiceRepresentation) name() string {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s udpServiceRepresentation) resourceType() string {
|
||||||
|
return s.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s udpServiceRepresentation) serversCount() int {
|
||||||
|
// TODO: maybe disable that data point altogether,
|
||||||
|
// if we can't/won't compute a fully correct (recursive) result.
|
||||||
|
// Or "redefine" it as only the top-level count?
|
||||||
|
// Note: The current algo is equivalent to the webui one.
|
||||||
|
if s.LoadBalancer == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(s.LoadBalancer.Servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s udpServiceRepresentation) provider() string {
|
||||||
|
return s.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s udpServiceRepresentation) status() string {
|
||||||
|
return s.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
type orderedMiddleware interface {
|
||||||
|
orderedWithName
|
||||||
|
|
||||||
|
resourceType() string
|
||||||
|
provider() string
|
||||||
|
status() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortMiddlewares[T orderedMiddleware](values url.Values, middlewares []T) {
|
||||||
|
sortBy := values.Get(sortByParam)
|
||||||
|
|
||||||
|
direction := values.Get(directionParam)
|
||||||
|
if direction == "" {
|
||||||
|
direction = ascendantSorting
|
||||||
|
}
|
||||||
|
|
||||||
|
switch sortBy {
|
||||||
|
case "name":
|
||||||
|
sortByName(direction, middlewares)
|
||||||
|
|
||||||
|
case "type":
|
||||||
|
sortByFunc(direction, middlewares, func(i int) string { return middlewares[i].resourceType() })
|
||||||
|
|
||||||
|
case "provider":
|
||||||
|
sortByFunc(direction, middlewares, func(i int) string { return middlewares[i].provider() })
|
||||||
|
|
||||||
|
case "status":
|
||||||
|
sortByFunc(direction, middlewares, func(i int) string { return middlewares[i].status() })
|
||||||
|
|
||||||
|
default:
|
||||||
|
sortByName(direction, middlewares)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m middlewareRepresentation) name() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m middlewareRepresentation) resourceType() string {
|
||||||
|
return m.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m middlewareRepresentation) provider() string {
|
||||||
|
return m.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m middlewareRepresentation) status() string {
|
||||||
|
return m.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tcpMiddlewareRepresentation) name() string {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tcpMiddlewareRepresentation) resourceType() string {
|
||||||
|
return m.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tcpMiddlewareRepresentation) provider() string {
|
||||||
|
return m.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m tcpMiddlewareRepresentation) status() string {
|
||||||
|
return m.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
type orderedByName interface {
|
||||||
|
orderedWithName
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortByName[T orderedByName](direction string, results []T) {
|
||||||
|
// Ascending
|
||||||
|
if direction == ascendantSorting {
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].name() < results[j].name()
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descending
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
return results[i].name() > results[j].name()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortByFunc[T orderedWithName, U constraints.Ordered](direction string, results []T, fn func(int) U) {
|
||||||
|
// Ascending
|
||||||
|
if direction == ascendantSorting {
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
if fn(i) == fn(j) {
|
||||||
|
return results[i].name() < results[j].name()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(i) < fn(j)
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descending
|
||||||
|
sort.Slice(results, func(i, j int) bool {
|
||||||
|
if fn(i) == fn(j) {
|
||||||
|
return results[i].name() > results[j].name()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(i) > fn(j)
|
||||||
|
})
|
||||||
|
}
|
1689
pkg/api/sort_test.go
Normal file
1689
pkg/api/sort_test.go
Normal file
File diff suppressed because it is too large
Load diff
36
pkg/api/testdata/routers-filtered-middlewareName.json
vendored
Normal file
36
pkg/api/testdata/routers-filtered-middlewareName.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"middlewares": [
|
||||||
|
"auth",
|
||||||
|
"addPrefixTest@anotherprovider"
|
||||||
|
],
|
||||||
|
"name": "bar@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"rule": "Host(`foo.bar`)",
|
||||||
|
"service": "foo-service@myprovider",
|
||||||
|
"status": "disabled",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"middlewares": [
|
||||||
|
"addPrefixTest",
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"name": "test@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"rule": "Host(`fii.bar.other`)",
|
||||||
|
"service": "fii-service@myprovider",
|
||||||
|
"status": "enabled",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
32
pkg/api/testdata/routers-filtered-serviceName.json
vendored
Normal file
32
pkg/api/testdata/routers-filtered-serviceName.json
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"name": "foo@otherprovider",
|
||||||
|
"provider": "otherprovider",
|
||||||
|
"rule": "Host(`fii.foo.other`)",
|
||||||
|
"service": "fii-service",
|
||||||
|
"status": "enabled",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"middlewares": [
|
||||||
|
"addPrefixTest",
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"name": "test@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"rule": "Host(`fii.bar.other`)",
|
||||||
|
"service": "fii-service@myprovider",
|
||||||
|
"status": "enabled",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
36
pkg/api/testdata/tcprouters-filtered-middlewareName.json
vendored
Normal file
36
pkg/api/testdata/tcprouters-filtered-middlewareName.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"middlewares": [
|
||||||
|
"auth",
|
||||||
|
"inflightconn@myprovider"
|
||||||
|
],
|
||||||
|
"name": "bar@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"rule": "Host(`foo.bar`)",
|
||||||
|
"service": "foo-service",
|
||||||
|
"status": "warning",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"middlewares": [
|
||||||
|
"inflightconn@myprovider",
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"name": "foo@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"rule": "Host(`foo.bar`)",
|
||||||
|
"service": "bar-service@myprovider",
|
||||||
|
"status": "disabled",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
31
pkg/api/testdata/tcprouters-filtered-serviceName.json
vendored
Normal file
31
pkg/api/testdata/tcprouters-filtered-serviceName.json
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"name": "bar@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"rule": "Host(`foo.bar`)",
|
||||||
|
"service": "foo-service",
|
||||||
|
"status": "warning",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"name": "test@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"rule": "Host(`foo.bar.other`)",
|
||||||
|
"service": "foo-service@myprovider",
|
||||||
|
"status": "enabled",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"tls": {
|
||||||
|
"passthrough": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
26
pkg/api/testdata/udprouters-filtered-serviceName.json
vendored
Normal file
26
pkg/api/testdata/udprouters-filtered-serviceName.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"name": "bar@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"service": "foo-service",
|
||||||
|
"status": "warning",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"entryPoints": [
|
||||||
|
"web"
|
||||||
|
],
|
||||||
|
"name": "test@myprovider",
|
||||||
|
"provider": "myprovider",
|
||||||
|
"service": "foo-service@myprovider",
|
||||||
|
"status": "enabled",
|
||||||
|
"using": [
|
||||||
|
"web"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
|
@ -209,6 +209,15 @@ func (cc *codeCatcher) Flush() {
|
||||||
// Otherwise, cc.code is actually a 200 here.
|
// Otherwise, cc.code is actually a 200 here.
|
||||||
cc.WriteHeader(cc.code)
|
cc.WriteHeader(cc.code)
|
||||||
|
|
||||||
|
// We don't care about the contents of the response,
|
||||||
|
// since we want to serve the ones from the error page,
|
||||||
|
// so we just don't flush.
|
||||||
|
// (e.g., To prevent superfluous WriteHeader on request with a
|
||||||
|
// `Transfert-Encoding: chunked` header).
|
||||||
|
if cc.caughtFilteredCode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if flusher, ok := cc.responseWriter.(http.Flusher); ok {
|
if flusher, ok := cc.responseWriter.(http.Flusher); ok {
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,10 +81,12 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
|
||||||
period = time.Second
|
period = time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
// if config.Average == 0, in that case,
|
// Initialized at rate.Inf to enforce no rate limiting when config.Average == 0
|
||||||
// the value of maxDelay does not matter since the reservation will (buggily) give us a delay of 0 anyway.
|
rtl := float64(rate.Inf)
|
||||||
|
// No need to set any particular value for maxDelay as the reservation's delay
|
||||||
|
// will be <= 0 in the Inf case (i.e. the average == 0 case).
|
||||||
var maxDelay time.Duration
|
var maxDelay time.Duration
|
||||||
var rtl float64
|
|
||||||
if config.Average > 0 {
|
if config.Average > 0 {
|
||||||
rtl = float64(config.Average*int64(time.Second)) / float64(period)
|
rtl = float64(config.Average*int64(time.Second)) / float64(period)
|
||||||
// maxDelay does not scale well for rates below 1,
|
// maxDelay does not scale well for rates below 1,
|
||||||
|
@ -155,10 +157,6 @@ func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// time/rate is bugged, since a rate.Limiter with a 0 Limit not only allows a Reservation to take place,
|
|
||||||
// but also gives a 0 delay below (because of a division by zero, followed by a multiplication that flips into the negatives),
|
|
||||||
// regardless of the current load.
|
|
||||||
// However, for now we take advantage of this behavior to provide the no-limit ratelimiter when config.Average is 0.
|
|
||||||
res := bucket.Reserve()
|
res := bucket.Reserve()
|
||||||
if !res.OK() {
|
if !res.OK() {
|
||||||
http.Error(rw, "No bursty traffic allowed", http.StatusTooManyRequests)
|
http.Error(rw, "No bursty traffic allowed", http.StatusTooManyRequests)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||||
"github.com/vulcand/oxy/v2/utils"
|
"github.com/vulcand/oxy/v2/utils"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewRateLimiter(t *testing.T) {
|
func TestNewRateLimiter(t *testing.T) {
|
||||||
|
@ -25,7 +26,16 @@ func TestNewRateLimiter(t *testing.T) {
|
||||||
expectedSourceIP string
|
expectedSourceIP string
|
||||||
requestHeader string
|
requestHeader string
|
||||||
expectedError string
|
expectedError string
|
||||||
|
expectedRTL rate.Limit
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
desc: "no ratelimit on Average == 0",
|
||||||
|
config: dynamic.RateLimit{
|
||||||
|
Average: 0,
|
||||||
|
Burst: 10,
|
||||||
|
},
|
||||||
|
expectedRTL: rate.Inf,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "maxDelay computation",
|
desc: "maxDelay computation",
|
||||||
config: dynamic.RateLimit{
|
config: dynamic.RateLimit{
|
||||||
|
@ -120,6 +130,9 @@ func TestNewRateLimiter(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, test.requestHeader, hd)
|
assert.Equal(t, test.requestHeader, hd)
|
||||||
}
|
}
|
||||||
|
if test.expectedRTL != 0 {
|
||||||
|
assert.Equal(t, test.expectedRTL, rtl.rate)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,12 @@ func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
http.NotFoundHandler().ServeHTTP(rw, req)
|
http.NotFoundHandler().ServeHTTP(rw, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRulePriority computes the priority for a given rule.
|
||||||
|
// The priority is calculated using the length of rule.
|
||||||
|
func GetRulePriority(rule string) int {
|
||||||
|
return len(rule)
|
||||||
|
}
|
||||||
|
|
||||||
// AddRoute add a new route to the router.
|
// AddRoute add a new route to the router.
|
||||||
func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error {
|
func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error {
|
||||||
parse, err := m.parser.Parse(rule)
|
parse, err := m.parser.Parse(rule)
|
||||||
|
@ -64,10 +70,6 @@ func (m *Muxer) AddRoute(rule string, priority int, handler http.Handler) error
|
||||||
return fmt.Errorf("error while adding rule %s: %w", rule, err)
|
return fmt.Errorf("error while adding rule %s: %w", rule, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if priority == 0 {
|
|
||||||
priority = len(rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.routes = append(m.routes, &route{
|
m.routes = append(m.routes, &route{
|
||||||
handler: handler,
|
handler: handler,
|
||||||
matchers: matchers,
|
matchers: matchers,
|
||||||
|
|
|
@ -376,6 +376,10 @@ func Test_addRoutePriority(t *testing.T) {
|
||||||
w.Header().Set("X-From", route.xFrom)
|
w.Header().Set("X-From", route.xFrom)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if route.priority == 0 {
|
||||||
|
route.priority = GetRulePriority(route.rule)
|
||||||
|
}
|
||||||
|
|
||||||
err := muxer.AddRoute(route.rule, route.priority, handler)
|
err := muxer.AddRoute(route.rule, route.priority, handler)
|
||||||
require.NoError(t, err, route.rule)
|
require.NoError(t, err, route.rule)
|
||||||
}
|
}
|
||||||
|
@ -517,3 +521,26 @@ func TestEmptyHost(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetRulePriority(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
rule string
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "simple rule",
|
||||||
|
rule: "Host(`example.org`)",
|
||||||
|
expected: 19,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, GetRulePriority(test.rule))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -72,6 +72,38 @@ func (m Muxer) Match(meta ConnData) (tcp.Handler, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRulePriority computes the priority for a given rule.
|
||||||
|
// The priority is calculated using the length of rule.
|
||||||
|
// There is a special case where the HostSNI(`*`) has a priority of -1.
|
||||||
|
func GetRulePriority(rule string) int {
|
||||||
|
catchAllParser, err := rules.NewParser([]string{"HostSNI"})
|
||||||
|
if err != nil {
|
||||||
|
return len(rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse, err := catchAllParser.Parse(rule)
|
||||||
|
if err != nil {
|
||||||
|
return len(rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTree, ok := parse.(rules.TreeBuilder)
|
||||||
|
if !ok {
|
||||||
|
return len(rule)
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleTree := buildTree()
|
||||||
|
|
||||||
|
// Special case for when the catchAll fallback is present.
|
||||||
|
// When no user-defined priority is found, the lowest computable priority minus one is used,
|
||||||
|
// in order to make the fallback the last to be evaluated.
|
||||||
|
if ruleTree.RuleLeft == nil && ruleTree.RuleRight == nil && len(ruleTree.Value) == 1 &&
|
||||||
|
ruleTree.Value[0] == "*" && strings.EqualFold(ruleTree.Matcher, "HostSNI") {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(rule)
|
||||||
|
}
|
||||||
|
|
||||||
// AddRoute adds a new route, associated to the given handler, at the given
|
// AddRoute adds a new route, associated to the given handler, at the given
|
||||||
// priority, to the muxer.
|
// priority, to the muxer.
|
||||||
func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error {
|
func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error {
|
||||||
|
@ -98,18 +130,6 @@ func (m *Muxer) AddRoute(rule string, priority int, handler tcp.Handler) error {
|
||||||
catchAll = ruleTree.Value[0] == "*" && strings.EqualFold(ruleTree.Matcher, "HostSNI")
|
catchAll = ruleTree.Value[0] == "*" && strings.EqualFold(ruleTree.Matcher, "HostSNI")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case for when the catchAll fallback is present.
|
|
||||||
// When no user-defined priority is found, the lowest computable priority minus one is used,
|
|
||||||
// in order to make the fallback the last to be evaluated.
|
|
||||||
if priority == 0 && catchAll {
|
|
||||||
priority = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default value, which means the user has not set it, so we'll compute it.
|
|
||||||
if priority == 0 {
|
|
||||||
priority = len(rule)
|
|
||||||
}
|
|
||||||
|
|
||||||
newRoute := &route{
|
newRoute := &route{
|
||||||
handler: handler,
|
handler: handler,
|
||||||
matchers: matchers,
|
matchers: matchers,
|
||||||
|
|
|
@ -444,6 +444,39 @@ func Test_Priority(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetRulePriority(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
rule string
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "simple rule",
|
||||||
|
rule: "HostSNI(`example.org`)",
|
||||||
|
expected: 22,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "HostSNI(`*`) rule",
|
||||||
|
rule: "HostSNI(`*`)",
|
||||||
|
expected: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "strange HostSNI(`*`) rule",
|
||||||
|
rule: " HostSNI ( `*` ) ",
|
||||||
|
expected: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, GetRulePriority(test.rule))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fakeConn struct {
|
type fakeConn struct {
|
||||||
call map[string]int
|
call map[string]int
|
||||||
remoteAddr net.Addr
|
remoteAddr net.Addr
|
||||||
|
|
|
@ -119,6 +119,10 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
|
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
|
||||||
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
|
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
|
||||||
|
|
||||||
|
if routerConfig.Priority == 0 {
|
||||||
|
routerConfig.Priority = httpmuxer.GetRulePriority(routerConfig.Rule)
|
||||||
|
}
|
||||||
|
|
||||||
handler, err := m.buildRouterHandler(ctxRouter, routerName, routerConfig)
|
handler, err := m.buildRouterHandler(ctxRouter, routerName, routerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
|
@ -126,8 +130,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = muxer.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
|
if err = muxer.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||||
if err != nil {
|
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error().Err(err).Send()
|
logger.Error().Err(err).Send()
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -264,6 +264,10 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
||||||
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
|
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerName).Logger()
|
||||||
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
|
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerName))
|
||||||
|
|
||||||
|
if routerConfig.Priority == 0 {
|
||||||
|
routerConfig.Priority = tcpmuxer.GetRulePriority(routerConfig.Rule)
|
||||||
|
}
|
||||||
|
|
||||||
if routerConfig.Service == "" {
|
if routerConfig.Service == "" {
|
||||||
err := errors.New("the service is missing on the router")
|
err := errors.New("the service is missing on the router")
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
|
@ -306,6 +310,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
||||||
|
|
||||||
if routerConfig.TLS == nil {
|
if routerConfig.TLS == nil {
|
||||||
logger.Debug().Msgf("Adding route for %q", routerConfig.Rule)
|
logger.Debug().Msgf("Adding route for %q", routerConfig.Rule)
|
||||||
|
|
||||||
if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error().Err(err).Send()
|
logger.Error().Err(err).Send()
|
||||||
|
@ -315,6 +320,7 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
||||||
|
|
||||||
if routerConfig.TLS.Passthrough {
|
if routerConfig.TLS.Passthrough {
|
||||||
logger.Debug().Msgf("Adding Passthrough route for %q", routerConfig.Rule)
|
logger.Debug().Msgf("Adding Passthrough route for %q", routerConfig.Rule)
|
||||||
|
|
||||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error().Err(err).Send()
|
logger.Error().Err(err).Send()
|
||||||
|
@ -349,11 +355,11 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
||||||
|
|
||||||
logger.Debug().Msgf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName)
|
logger.Debug().Msgf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName)
|
||||||
|
|
||||||
err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{})
|
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{}); err != nil {
|
||||||
if err != nil {
|
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error().Err(err).Send()
|
logger.Error().Err(err).Send()
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,10 +389,10 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
||||||
|
|
||||||
logger.Debug().Msgf("Adding TLS route for %q", routerConfig.Rule)
|
logger.Debug().Msgf("Adding TLS route for %q", routerConfig.Rule)
|
||||||
|
|
||||||
err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
|
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||||
if err != nil {
|
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error().Err(err).Send()
|
logger.Error().Err(err).Send()
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -25,7 +27,10 @@ func isPostgres(br *bufio.Reader) (bool, error) {
|
||||||
for i := 1; i < len(PostgresStartTLSMsg)+1; i++ {
|
for i := 1; i < len(PostgresStartTLSMsg)+1; i++ {
|
||||||
peeked, err := br.Peek(i)
|
peeked, err := br.Peek(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Error while Peeking first bytes")
|
var opErr *net.OpError
|
||||||
|
if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || opErr.Timeout()) {
|
||||||
|
log.Error().Err(err).Msg("Error while Peeking first byte")
|
||||||
|
}
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -268,8 +268,7 @@ func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
||||||
|
|
||||||
// muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
|
// muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
|
||||||
// so there's no need for specifying a priority for them.
|
// so there's no need for specifying a priority for them.
|
||||||
err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler)
|
if err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Error while adding route for host")
|
log.Error().Err(err).Msg("Error while adding route for host")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ const allColumns = [
|
||||||
required: true,
|
required: true,
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
fieldToProps: row => ({
|
fieldToProps: row => ({
|
||||||
state: row.status === 'enabled' ? 'positive' : 'negative'
|
state: row.status === 'enabled' ? 'positive' : 'negative'
|
||||||
}),
|
}),
|
||||||
|
@ -20,6 +21,7 @@ const allColumns = [
|
||||||
name: 'tls',
|
name: 'tls',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'TLS',
|
label: 'TLS',
|
||||||
|
sortable: false,
|
||||||
fieldToProps: row => ({ isTLS: row.tls }),
|
fieldToProps: row => ({ isTLS: row.tls }),
|
||||||
component: TLSState
|
component: TLSState
|
||||||
},
|
},
|
||||||
|
@ -27,6 +29,7 @@ const allColumns = [
|
||||||
name: 'rule',
|
name: 'rule',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Rule',
|
label: 'Rule',
|
||||||
|
sortable: true,
|
||||||
component: QChip,
|
component: QChip,
|
||||||
fieldToProps: () => ({ class: 'app-chip app-chip-rule', dense: true }),
|
fieldToProps: () => ({ class: 'app-chip app-chip-rule', dense: true }),
|
||||||
content: row => row.rule
|
content: row => row.rule
|
||||||
|
@ -35,6 +38,7 @@ const allColumns = [
|
||||||
name: 'entryPoints',
|
name: 'entryPoints',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Entrypoints',
|
label: 'Entrypoints',
|
||||||
|
sortable: true,
|
||||||
component: Chips,
|
component: Chips,
|
||||||
fieldToProps: row => ({
|
fieldToProps: row => ({
|
||||||
classNames: 'app-chip app-chip-entry-points',
|
classNames: 'app-chip app-chip-entry-points',
|
||||||
|
@ -46,6 +50,7 @@ const allColumns = [
|
||||||
name: 'name',
|
name: 'name',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
|
sortable: true,
|
||||||
component: QChip,
|
component: QChip,
|
||||||
fieldToProps: () => ({ class: 'app-chip app-chip-name', dense: true }),
|
fieldToProps: () => ({ class: 'app-chip app-chip-name', dense: true }),
|
||||||
content: row => row.name
|
content: row => row.name
|
||||||
|
@ -54,6 +59,7 @@ const allColumns = [
|
||||||
name: 'type',
|
name: 'type',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Type',
|
label: 'Type',
|
||||||
|
sortable: true,
|
||||||
component: QChip,
|
component: QChip,
|
||||||
fieldToProps: () => ({
|
fieldToProps: () => ({
|
||||||
class: 'app-chip app-chip-entry-points',
|
class: 'app-chip app-chip-entry-points',
|
||||||
|
@ -65,6 +71,7 @@ const allColumns = [
|
||||||
name: 'servers',
|
name: 'servers',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
label: 'Servers',
|
label: 'Servers',
|
||||||
|
sortable: true,
|
||||||
fieldToProps: () => ({ class: 'servers-label' }),
|
fieldToProps: () => ({ class: 'servers-label' }),
|
||||||
content: function (value) {
|
content: function (value) {
|
||||||
if (value.loadBalancer && value.loadBalancer.servers) {
|
if (value.loadBalancer && value.loadBalancer.servers) {
|
||||||
|
@ -78,6 +85,7 @@ const allColumns = [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Service',
|
label: 'Service',
|
||||||
component: QChip,
|
component: QChip,
|
||||||
|
sortable: true,
|
||||||
fieldToProps: () => ({ class: 'app-chip app-chip-service', dense: true }),
|
fieldToProps: () => ({ class: 'app-chip app-chip-service', dense: true }),
|
||||||
content: row => row.service
|
content: row => row.service
|
||||||
},
|
},
|
||||||
|
@ -85,8 +93,23 @@ const allColumns = [
|
||||||
name: 'provider',
|
name: 'provider',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
label: 'Provider',
|
label: 'Provider',
|
||||||
|
sortable: true,
|
||||||
fieldToProps: row => ({ name: row.provider }),
|
fieldToProps: row => ({ name: row.provider }),
|
||||||
component: ProviderIcon
|
component: ProviderIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Priority',
|
||||||
|
sortable: true,
|
||||||
|
component: QChip,
|
||||||
|
fieldToProps: () => ({ class: 'app-chip app-chip-accent', dense: true }),
|
||||||
|
content: row => {
|
||||||
|
return {
|
||||||
|
short: String(row.priority).length > 10 ? String(row.priority).substring(0, 10) + '...' : row.priority,
|
||||||
|
long: row.priority
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -98,7 +121,8 @@ const columnsByResource = {
|
||||||
'name',
|
'name',
|
||||||
'service',
|
'service',
|
||||||
'tls',
|
'tls',
|
||||||
'provider'
|
'provider',
|
||||||
|
'priority'
|
||||||
],
|
],
|
||||||
udpRouters: ['status', 'entryPoints', 'name', 'service', 'provider'],
|
udpRouters: ['status', 'entryPoints', 'name', 'service', 'provider'],
|
||||||
services: ['status', 'name', 'type', 'servers', 'provider'],
|
services: ['status', 'name', 'type', 'servers', 'provider'],
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getTotal } from './utils'
|
||||||
const apiBase = '/http'
|
const apiBase = '/http'
|
||||||
|
|
||||||
function getAllRouters (params) {
|
function getAllRouters (params) {
|
||||||
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}&serviceName=${params.serviceName}&middlewareName=${params.middlewareName}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
@ -22,7 +22,7 @@ function getRouterByName (name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllServices (params) {
|
function getAllServices (params) {
|
||||||
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
@ -40,7 +40,7 @@ function getServiceByName (name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllMiddlewares (params) {
|
function getAllMiddlewares (params) {
|
||||||
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getTotal } from './utils'
|
||||||
const apiBase = '/tcp'
|
const apiBase = '/tcp'
|
||||||
|
|
||||||
function getAllRouters (params) {
|
function getAllRouters (params) {
|
||||||
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}&serviceName=${params.serviceName}&middlewareName=${params.middlewareName}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
@ -22,7 +22,7 @@ function getRouterByName (name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllServices (params) {
|
function getAllServices (params) {
|
||||||
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
@ -40,7 +40,7 @@ function getServiceByName (name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllMiddlewares (params) {
|
function getAllMiddlewares (params) {
|
||||||
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/middlewares?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getTotal } from './utils'
|
||||||
const apiBase = '/udp'
|
const apiBase = '/udp'
|
||||||
|
|
||||||
function getAllRouters (params) {
|
function getAllRouters (params) {
|
||||||
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/routers?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}&serviceName=${params.serviceName}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
@ -22,7 +22,7 @@ function getRouterByName (name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllServices (params) {
|
function getAllServices (params) {
|
||||||
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}`)
|
return APP.api.get(`${apiBase}/services?search=${params.query}&status=${params.status}&per_page=${params.limit}&page=${params.page}&sortBy=${params.sortBy}&direction=${params.direction}`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const { data = [], headers } = response
|
const { data = [], headers } = response
|
||||||
const total = getTotal(headers, params)
|
const total = getTotal(headers, params)
|
||||||
|
|
|
@ -6,9 +6,12 @@
|
||||||
<tr class="table-header">
|
<tr class="table-header">
|
||||||
<th
|
<th
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
v-bind:class="`text-${column.align}`"
|
v-bind:class="getColumn(column.name).sortable ? `text-${column.align} cursor-pointer`: `text-${column.align}`"
|
||||||
v-bind:key="column.name">
|
v-bind:key="column.name"
|
||||||
|
@click="getColumn(column.name).sortable ? onSortClick(column.name) : null">
|
||||||
{{ column.label }}
|
{{ column.label }}
|
||||||
|
<i v-if="currentSort === column.name" class="material-icons">{{currentSortDir === 'asc' ? 'arrow_drop_down' : 'arrow_drop_up'}}</i>
|
||||||
|
<i v-else style="opacity: 0" class="material-icons">{{currentSortDir === 'asc' ? 'arrow_drop_down' : 'arrow_drop_up'}}</i>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -27,9 +30,19 @@
|
||||||
v-bind:is="getColumn(column.name).component"
|
v-bind:is="getColumn(column.name).component"
|
||||||
v-bind="getColumn(column.name).fieldToProps(row)"
|
v-bind="getColumn(column.name).fieldToProps(row)"
|
||||||
>
|
>
|
||||||
<template v-if="getColumn(column.name).content">
|
<template v-if="getColumn(column.name).content && column.name !== 'priority'">
|
||||||
{{ getColumn(column.name).content(row) }}
|
{{ getColumn(column.name).content(row) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="getColumn(column.name).content && column.name === 'priority'">
|
||||||
|
<div>
|
||||||
|
{{ getColumn(column.name).content(row).short }}
|
||||||
|
</div>
|
||||||
|
<q-tooltip anchor="top middle" self="bottom middle" :offset="[10, 10]">
|
||||||
|
<div class="priority-tooltip">
|
||||||
|
{{ getColumn(column.name).content(row).long }}
|
||||||
|
</div>
|
||||||
|
</q-tooltip>
|
||||||
|
</template>
|
||||||
</component>
|
</component>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
|
@ -72,6 +85,12 @@ export default {
|
||||||
QSpinnerDots,
|
QSpinnerDots,
|
||||||
QPageScroller
|
QPageScroller
|
||||||
},
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
currentSort: 'name',
|
||||||
|
currentSortDir: 'asc'
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getColumn (columnName) {
|
getColumn (columnName) {
|
||||||
return this.columns.find(c => c.name === columnName) || {}
|
return this.columns.find(c => c.name === columnName) || {}
|
||||||
|
@ -80,6 +99,14 @@ export default {
|
||||||
this.onLoadMore({ page: index })
|
this.onLoadMore({ page: index })
|
||||||
.then(() => done())
|
.then(() => done())
|
||||||
.catch(() => done(true))
|
.catch(() => done(true))
|
||||||
|
},
|
||||||
|
onSortClick (s) {
|
||||||
|
if (s === this.currentSort) {
|
||||||
|
this.currentSortDir = this.currentSortDir === 'asc' ? 'desc' : 'asc'
|
||||||
|
}
|
||||||
|
this.currentSort = s
|
||||||
|
this.$emit('update:currentSort', s)
|
||||||
|
this.$emit('update:currentSortDir', this.currentSortDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,4 +154,8 @@ export default {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.priority-tooltip{
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -73,6 +73,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
<q-card-section v-if="data.priority">
|
||||||
|
<div class="row items-start no-wrap">
|
||||||
|
<div class="col">
|
||||||
|
<div class="text-subtitle2">PRIORITY</div>
|
||||||
|
<q-chip
|
||||||
|
dense
|
||||||
|
class="app-chip app-chip-entry-points">
|
||||||
|
{{ data.priority }}
|
||||||
|
</q-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
<q-card-section v-if="data.error">
|
<q-card-section v-if="data.error">
|
||||||
<div class="row items-start no-wrap">
|
<div class="row items-start no-wrap">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
|
@ -44,12 +44,15 @@
|
||||||
<div class="row items-center q-col-gutter-lg">
|
<div class="row items-center q-col-gutter-lg">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<main-table
|
<main-table
|
||||||
:data="allRouters"
|
|
||||||
v-bind="getTableProps({ type: `${protocol}-routers` })"
|
v-bind="getTableProps({ type: `${protocol}-routers` })"
|
||||||
|
:data="allRouters"
|
||||||
|
:onLoadMore="onGetAll"
|
||||||
:request="()=>{}"
|
:request="()=>{}"
|
||||||
:loading="routersLoading"
|
:loading="routersLoading"
|
||||||
:pagination.sync="routersPagination"
|
:pagination.sync="routersPagination"
|
||||||
:filter="routersFilter"
|
:filter="routersFilter"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -91,7 +94,11 @@ export default {
|
||||||
page: 1,
|
page: 1,
|
||||||
rowsPerPage: 1000,
|
rowsPerPage: 1000,
|
||||||
rowsNumber: 0
|
rowsNumber: 0
|
||||||
}
|
},
|
||||||
|
filter: '',
|
||||||
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -108,11 +115,14 @@ export default {
|
||||||
},
|
},
|
||||||
getRouterByName () {
|
getRouterByName () {
|
||||||
return this[`${this.protocol}_getRouterByName`]
|
return this[`${this.protocol}_getRouterByName`]
|
||||||
|
},
|
||||||
|
getAllRouters () {
|
||||||
|
return this[`${this.protocol}_getAllRouters`]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('http', { http_getMiddlewareByName: 'getMiddlewareByName', http_getRouterByName: 'getRouterByName' }),
|
...mapActions('http', { http_getMiddlewareByName: 'getMiddlewareByName', http_getRouterByName: 'getRouterByName', http_getAllRouters: 'getAllRouters' }),
|
||||||
...mapActions('tcp', { tcp_getMiddlewareByName: 'getMiddlewareByName', tcp_getRouterByName: 'getRouterByName' }),
|
...mapActions('tcp', { tcp_getMiddlewareByName: 'getMiddlewareByName', tcp_getRouterByName: 'getRouterByName', tcp_getAllRouters: 'getAllRouters' }),
|
||||||
refreshAll () {
|
refreshAll () {
|
||||||
if (this.middlewareByName.loading) {
|
if (this.middlewareByName.loading) {
|
||||||
return
|
return
|
||||||
|
@ -127,22 +137,26 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get routers
|
// Get routers
|
||||||
if (body.usedBy) {
|
this.getAllRouters({
|
||||||
for (const router in body.usedBy) {
|
query: this.filter,
|
||||||
if (body.usedBy.hasOwnProperty(router)) {
|
status: this.status,
|
||||||
this.getRouterByName(body.usedBy[router])
|
page: 1,
|
||||||
|
limit: 1000,
|
||||||
|
middlewareName: this.name,
|
||||||
|
serviceName: '',
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir
|
||||||
|
})
|
||||||
.then(body => {
|
.then(body => {
|
||||||
|
this.allRouters = []
|
||||||
if (body) {
|
if (body) {
|
||||||
this.routersLoading = false
|
this.routersLoading = false
|
||||||
this.allRouters.push(body)
|
this.allRouters.push(...body.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('Error -> routers/byName', error)
|
console.log('Error -> routers/byName', error)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clearTimeout(this.timeOutGetAll)
|
clearTimeout(this.timeOutGetAll)
|
||||||
this.timeOutGetAll = setTimeout(() => {
|
this.timeOutGetAll = setTimeout(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
@ -153,12 +167,18 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {},
|
||||||
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
clearInterval(this.timeOutGetAll)
|
clearInterval(this.timeOutGetAll)
|
||||||
this.$store.commit('http/getMiddlewareByNameClear')
|
this.$store.commit('http/getMiddlewareByNameClear')
|
||||||
|
|
|
@ -112,12 +112,15 @@
|
||||||
<div class="row items-center q-col-gutter-lg">
|
<div class="row items-center q-col-gutter-lg">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<main-table
|
<main-table
|
||||||
:data="allRouters"
|
|
||||||
v-bind="getTableProps({ type: `${protocol}-routers` })"
|
v-bind="getTableProps({ type: `${protocol}-routers` })"
|
||||||
|
:data="allRouters"
|
||||||
|
:onLoadMore="onGetAll"
|
||||||
:request="()=>{}"
|
:request="()=>{}"
|
||||||
:loading="routersLoading"
|
:loading="routersLoading"
|
||||||
:pagination.sync="routersPagination"
|
:pagination.sync="routersPagination"
|
||||||
:filter="routersFilter"
|
:filter="routersFilter"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -167,7 +170,11 @@ export default {
|
||||||
page: 1,
|
page: 1,
|
||||||
rowsPerPage: 1000,
|
rowsPerPage: 1000,
|
||||||
rowsNumber: 0
|
rowsNumber: 0
|
||||||
}
|
},
|
||||||
|
filter: '',
|
||||||
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -185,12 +192,15 @@ export default {
|
||||||
},
|
},
|
||||||
getRouterByName () {
|
getRouterByName () {
|
||||||
return this[`${this.protocol}_getRouterByName`]
|
return this[`${this.protocol}_getRouterByName`]
|
||||||
|
},
|
||||||
|
getAllRouters () {
|
||||||
|
return this[`${this.protocol}_getAllRouters`]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('http', { http_getServiceByName: 'getServiceByName', http_getRouterByName: 'getRouterByName' }),
|
...mapActions('http', { http_getServiceByName: 'getServiceByName', http_getRouterByName: 'getRouterByName', http_getAllRouters: 'getAllRouters' }),
|
||||||
...mapActions('tcp', { tcp_getServiceByName: 'getServiceByName', tcp_getRouterByName: 'getRouterByName' }),
|
...mapActions('tcp', { tcp_getServiceByName: 'getServiceByName', tcp_getRouterByName: 'getRouterByName', tcp_getAllRouters: 'getAllRouters' }),
|
||||||
...mapActions('udp', { udp_getServiceByName: 'getServiceByName', udp_getRouterByName: 'getRouterByName' }),
|
...mapActions('udp', { udp_getServiceByName: 'getServiceByName', udp_getRouterByName: 'getRouterByName', udp_getAllRouters: 'getAllRouters' }),
|
||||||
refreshAll () {
|
refreshAll () {
|
||||||
if (this.serviceByName.loading) {
|
if (this.serviceByName.loading) {
|
||||||
return
|
return
|
||||||
|
@ -205,22 +215,26 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get routers
|
// Get routers
|
||||||
if (body.usedBy) {
|
this.getAllRouters({
|
||||||
for (const router in body.usedBy) {
|
query: this.filter,
|
||||||
if (body.usedBy.hasOwnProperty(router)) {
|
status: this.status,
|
||||||
this.getRouterByName(body.usedBy[router])
|
page: 1,
|
||||||
|
limit: 1000,
|
||||||
|
middlewareName: '',
|
||||||
|
serviceName: this.name,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir
|
||||||
|
})
|
||||||
.then(body => {
|
.then(body => {
|
||||||
|
this.allRouters = []
|
||||||
if (body) {
|
if (body) {
|
||||||
this.routersLoading = false
|
this.routersLoading = false
|
||||||
this.allRouters.push(body)
|
this.allRouters.push(...body.data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('Error -> routers/byName', error)
|
console.log('Error -> getAllRouters', error)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clearTimeout(this.timeOutGetAll)
|
clearTimeout(this.timeOutGetAll)
|
||||||
this.timeOutGetAll = setTimeout(() => {
|
this.timeOutGetAll = setTimeout(() => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
@ -231,12 +245,18 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {},
|
||||||
|
|
||||||
},
|
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
clearInterval(this.timeOutGetAll)
|
clearInterval(this.timeOutGetAll)
|
||||||
this.$store.commit('http/getServiceByNameClear')
|
this.$store.commit('http/getServiceByNameClear')
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allMiddlewares.endReached"
|
:endReached="allMiddlewares.endReached"
|
||||||
:loading="allMiddlewares.loading"
|
:loading="allMiddlewares.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -62,6 +66,8 @@ export default {
|
||||||
return this.getAllMiddlewares({
|
return this.getAllMiddlewares({
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -82,6 +88,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allRouters.endReached"
|
:endReached="allRouters.endReached"
|
||||||
:loading="allRouters.loading"
|
:loading="allRouters.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -60,8 +64,12 @@ export default {
|
||||||
...mapActions('http', { getAllRouters: 'getAllRouters' }),
|
...mapActions('http', { getAllRouters: 'getAllRouters' }),
|
||||||
getAllRoutersWithParams (params) {
|
getAllRoutersWithParams (params) {
|
||||||
return this.getAllRouters({
|
return this.getAllRouters({
|
||||||
|
serviceName: '',
|
||||||
|
middlewareName: '',
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -82,6 +90,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allServices.endReached"
|
:endReached="allServices.endReached"
|
||||||
:loading="allServices.loading"
|
:loading="allServices.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -62,6 +66,8 @@ export default {
|
||||||
return this.getAllServices({
|
return this.getAllServices({
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -82,6 +88,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allMiddlewares.endReached"
|
:endReached="allMiddlewares.endReached"
|
||||||
:loading="allMiddlewares.loading"
|
:loading="allMiddlewares.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -62,6 +66,8 @@ export default {
|
||||||
return this.getAllMiddlewares({
|
return this.getAllMiddlewares({
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -82,6 +88,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allRouters.endReached"
|
:endReached="allRouters.endReached"
|
||||||
:loading="allRouters.loading"
|
:loading="allRouters.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -60,8 +64,12 @@ export default {
|
||||||
...mapActions('tcp', { getAllRouters: 'getAllRouters' }),
|
...mapActions('tcp', { getAllRouters: 'getAllRouters' }),
|
||||||
getAllRoutersWithParams (params) {
|
getAllRoutersWithParams (params) {
|
||||||
return this.getAllRouters({
|
return this.getAllRouters({
|
||||||
|
serviceName: '',
|
||||||
|
middlewareName: '',
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -82,6 +90,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allServices.endReached"
|
:endReached="allServices.endReached"
|
||||||
:loading="allServices.loading"
|
:loading="allServices.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -62,6 +66,8 @@ export default {
|
||||||
return this.getAllServices({
|
return this.getAllServices({
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -82,6 +88,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allRouters.endReached"
|
:endReached="allRouters.endReached"
|
||||||
:loading="allRouters.loading"
|
:loading="allRouters.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +51,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -61,6 +65,10 @@ export default {
|
||||||
return this.getAllRouters({
|
return this.getAllRouters({
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
|
serviceName: '',
|
||||||
|
middlewareName: '',
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -81,6 +89,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
:onLoadMore="handleLoadMore"
|
:onLoadMore="handleLoadMore"
|
||||||
:endReached="allServices.endReached"
|
:endReached="allServices.endReached"
|
||||||
:loading="allServices.loading"
|
:loading="allServices.loading"
|
||||||
|
:currentSort.sync="sortBy"
|
||||||
|
:currentSortDir.sync="sortDir"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -50,7 +52,9 @@ export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
status: ''
|
status: '',
|
||||||
|
sortBy: 'name',
|
||||||
|
sortDir: 'asc'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -62,6 +66,8 @@ export default {
|
||||||
return this.getAllServices({
|
return this.getAllServices({
|
||||||
query: this.filter,
|
query: this.filter,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
|
sortBy: this.sortBy,
|
||||||
|
direction: this.sortDir,
|
||||||
...params
|
...params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -82,6 +88,12 @@ export default {
|
||||||
},
|
},
|
||||||
'filter' () {
|
'filter' () {
|
||||||
this.refreshAll()
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortBy' () {
|
||||||
|
this.refreshAll()
|
||||||
|
},
|
||||||
|
'sortDir' () {
|
||||||
|
this.refreshAll()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
|
|
Loading…
Reference in a new issue