Added router priority to webui's list and detail page

This commit is contained in:
bendre90 2023-01-09 17:24:05 +01:00 committed by GitHub
parent cd90b9761a
commit 8cd4923e72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 2913 additions and 131 deletions

View file

@ -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"

View file

@ -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"

View file

@ -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
}, },

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -22,8 +22,10 @@ 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 {

View file

@ -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 {

View file

@ -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",

View file

@ -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 {

View file

@ -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",

View file

@ -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 {

View file

@ -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
View 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

File diff suppressed because it is too large Load diff

View 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"
]
}
]

View 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"
]
}
]

View 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"
]
}
]

View 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
}
}
]

View 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"
]
}
]

View file

@ -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,

View file

@ -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))
})
}
}

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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
} }
} }
} }

View file

@ -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")
} }
} }

View file

@ -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'],

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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>

View file

@ -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">

View file

@ -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,
.then(body => { limit: 1000,
if (body) { middlewareName: this.name,
this.routersLoading = false serviceName: '',
this.allRouters.push(body) sortBy: this.sortBy,
} direction: this.sortDir
}) })
.catch(error => { .then(body => {
console.log('Error -> routers/byName', error) this.allRouters = []
}) if (body) {
this.routersLoading = false
this.allRouters.push(...body.data)
} }
} })
} .catch(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')

View file

@ -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,
.then(body => { limit: 1000,
if (body) { middlewareName: '',
this.routersLoading = false serviceName: this.name,
this.allRouters.push(body) sortBy: this.sortBy,
} direction: this.sortDir
}) })
.catch(error => { .then(body => {
console.log('Error -> routers/byName', error) this.allRouters = []
}) if (body) {
this.routersLoading = false
this.allRouters.push(...body.data)
} }
} })
} .catch(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')

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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 () {

View file

@ -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 () {