Repository: containous/traefik
Pull request #5267 (2019-08-30 11:30:55)

Improve API for the web UI
### What does this PR do?

- add search filters (only in names for now).
- add type field for:
  - HTTP services
  - HTTP middlewares
  - TCP services
- HTTP errors with JSON responses. 
- add `Using` for entry points on routers.
- fix(tcp): Add errors about unknown entryPoint in runtime api 

### Motivation

Required for the web UI.

### More

- [x] Added/updated tests
- [ ] ~~Added/updated documentation~~



Patch:
diff --git a/pkg/api/criterion.go b/pkg/api/criterion.go
new file mode 100644
index 000000000..d81a9f717
--- /dev/null
+++ b/pkg/api/criterion.go
@@ -0,0 +1,102 @@
+package api
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+const (
+	defaultPerPage = 100
+	defaultPage    = 1
+)
+
+const nextPageHeader = "X-Next-Page"
+
+type pageInfo struct {
+	startIndex int
+	endIndex   int
+	nextPage   int
+}
+
+type searchCriterion struct {
+	Search string `url:"search"`
+	Status string `url:"status"`
+}
+
+func newSearchCriterion(query url.Values) *searchCriterion {
+	if len(query) == 0 {
+		return nil
+	}
+
+	search := query.Get("search")
+	status := query.Get("status")
+
+	if status == "" && search == "" {
+		return nil
+	}
+
+	return &searchCriterion{Search: search, Status: status}
+}
+
+func (c *searchCriterion) withStatus(name string) bool {
+	return c.Status == "" || strings.EqualFold(name, c.Status)
+}
+
+func (c *searchCriterion) searchIn(values ...string) bool {
+	if c.Search == "" {
+		return true
+	}
+
+	for _, v := range values {
+		if strings.Contains(strings.ToLower(v), strings.ToLower(c.Search)) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func pagination(request *http.Request, max int) (pageInfo, error) {
+	perPage, err := getIntParam(request, "per_page", defaultPerPage)
+	if err != nil {
+		return pageInfo{}, err
+	}
+
+	page, err := getIntParam(request, "page", defaultPage)
+	if err != nil {
+		return pageInfo{}, err
+	}
+
+	startIndex := (page - 1) * perPage
+	if startIndex != 0 && startIndex >= max {
+		return pageInfo{}, fmt.Errorf("invalid request: page: %d, per_page: %d", page, perPage)
+	}
+
+	endIndex := startIndex + perPage
+	if endIndex >= max {
+		endIndex = max
+	}
+
+	nextPage := 1
+	if page*perPage < max {
+		nextPage = page + 1
+	}
+
+	return pageInfo{startIndex: startIndex, endIndex: endIndex, nextPage: nextPage}, nil
+}
+
+func getIntParam(request *http.Request, key string, defaultValue int) (int, error) {
+	raw := request.URL.Query().Get(key)
+	if raw == "" {
+		return defaultValue, nil
+	}
+
+	value, err := strconv.Atoi(raw)
+	if err != nil || value <= 0 {
+		return 0, fmt.Errorf("invalid request: %s: %d", key, value)
+	}
+	return value, nil
+}
diff --git a/pkg/api/handler.go b/pkg/api/handler.go
index 7ee900c75..4b707c568 100644
--- a/pkg/api/handler.go
+++ b/pkg/api/handler.go
@@ -2,9 +2,8 @@ package api
 
 import (
 	"encoding/json"
-	"fmt"
 	"net/http"
-	"strconv"
+	"reflect"
 	"strings"
 
 	"github.com/containous/traefik/v2/pkg/config/runtime"
@@ -15,12 +14,19 @@ import (
 	"github.com/gorilla/mux"
 )
 
-const (
-	defaultPerPage = 100
-	defaultPage    = 1
-)
+type apiError struct {
+	Message string `json:"message"`
+}
+
+func writeError(rw http.ResponseWriter, msg string, code int) {
+	data, err := json.Marshal(apiError{Message: msg})
+	if err != nil {
+		http.Error(rw, msg, code)
+		return
+	}
 
-const nextPageHeader = "X-Next-Page"
+	http.Error(rw, string(data), code)
+}
 
 type serviceInfoRepresentation struct {
 	*runtime.ServiceInfo
@@ -36,12 +42,6 @@ type RunTimeRepresentation struct {
 	TCPServices map[string]*runtime.TCPServiceInfo    `json:"tcpServices,omitempty"`
 }
 
-type pageInfo struct {
-	startIndex int
-	endIndex   int
-	nextPage   int
-}
-
 // Handler serves the configuration and status of Traefik on API endpoints.
 type Handler struct {
 	dashboard bool
@@ -136,48 +136,19 @@ func (h Handler) getRuntimeConfiguration(rw http.ResponseWriter, request *http.R
 	}
 }
 
-func pagination(request *http.Request, max int) (pageInfo, error) {
-	perPage, err := getIntParam(request, "per_page", defaultPerPage)
-	if err != nil {
-		return pageInfo{}, err
-	}
-
-	page, err := getIntParam(request, "page", defaultPage)
-	if err != nil {
-		return pageInfo{}, err
-	}
-
-	startIndex := (page - 1) * perPage
-	if startIndex != 0 && startIndex >= max {
-		return pageInfo{}, fmt.Errorf("invalid request: page: %d, per_page: %d", page, perPage)
-	}
-
-	endIndex := startIndex + perPage
-	if endIndex >= max {
-		endIndex = max
-	}
-
-	nextPage := 1
-	if page*perPage < max {
-		nextPage = page + 1
-	}
-
-	return pageInfo{startIndex: startIndex, endIndex: endIndex, nextPage: nextPage}, nil
+func getProviderName(id string) string {
+	return strings.SplitN(id, "@", 2)[1]
 }
 
-func getIntParam(request *http.Request, key string, defaultValue int) (int, error) {
-	raw := request.URL.Query().Get(key)
-	if raw == "" {
-		return defaultValue, nil
-	}
-
-	value, err := strconv.Atoi(raw)
-	if err != nil || value <= 0 {
-		return 0, fmt.Errorf("invalid request: %s: %d", key, value)
+func extractType(element interface{}) string {
+	v := reflect.ValueOf(element).Elem()
+	for i := 0; i < v.NumField(); i++ {
+		field := v.Field(i)
+		if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
+			if !field.IsNil() {
+				return v.Type().Field(i).Name
+			}
+		}
 	}
-	return value, nil
-}
-
-func getProviderName(id string) string {
-	return strings.SplitN(id, "@", 2)[1]
+	return ""
 }
diff --git a/pkg/api/handler_entrypoint.go b/pkg/api/handler_entrypoint.go
index b4cc3f4e5..808b7e536 100644
--- a/pkg/api/handler_entrypoint.go
+++ b/pkg/api/handler_entrypoint.go
@@ -2,6 +2,7 @@ package api
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"sort"
 	"strconv"
@@ -30,28 +31,31 @@ func (h Handler) getEntryPoints(rw http.ResponseWriter, request *http.Request) {
 		return results[i].Name < results[j].Name
 	})
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	pageInfo, err := pagination(request, len(results))
 	if err != nil {
-		http.Error(rw, err.Error(), http.StatusBadRequest)
+		writeError(rw, err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
 	rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
 
 	err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) {
 	entryPointID := mux.Vars(request)["entryPointID"]
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	ep, ok := h.staticConfig.EntryPoints[entryPointID]
 	if !ok {
-		http.NotFound(rw, request)
+		writeError(rw, fmt.Sprintf("entry point not found: %s", entryPointID), http.StatusNotFound)
 		return
 	}
 
@@ -60,11 +64,9 @@ func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) {
 		Name:       entryPointID,
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
-
 	err := json.NewEncoder(rw).Encode(result)
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
diff --git a/pkg/api/handler_http.go b/pkg/api/handler_http.go
index 39bc3fd3f..fe43c215f 100644
--- a/pkg/api/handler_http.go
+++ b/pkg/api/handler_http.go
@@ -2,9 +2,11 @@ package api
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"sort"
 	"strconv"
+	"strings"
 
 	"github.com/containous/traefik/v2/pkg/config/runtime"
 	"github.com/containous/traefik/v2/pkg/log"
@@ -17,182 +19,224 @@ type routerRepresentation struct {
 	Provider string `json:"provider,omitempty"`
 }
 
+func newRouterRepresentation(name string, rt *runtime.RouterInfo) routerRepresentation {
+	return routerRepresentation{
+		RouterInfo: rt,
+		Name:       name,
+		Provider:   getProviderName(name),
+	}
+}
+
 type serviceRepresentation struct {
 	*runtime.ServiceInfo
 	ServerStatus map[string]string `json:"serverStatus,omitempty"`
 	Name         string            `json:"name,omitempty"`
 	Provider     string            `json:"provider,omitempty"`
+	Type         string            `json:"type,omitempty"`
+}
+
+func newServiceRepresentation(name string, si *runtime.ServiceInfo) serviceRepresentation {
+	return serviceRepresentation{
+		ServiceInfo:  si,
+		Name:         name,
+		Provider:     getProviderName(name),
+		ServerStatus: si.GetAllStatus(),
+		Type:         strings.ToLower(extractType(si.Service)),
+	}
 }
 
 type middlewareRepresentation struct {
 	*runtime.MiddlewareInfo
 	Name     string `json:"name,omitempty"`
 	Provider string `json:"provider,omitempty"`
+	Type     string `json:"type,omitempty"`
+}
+
+func newMiddlewareRepresentation(name string, mi *runtime.MiddlewareInfo) middlewareRepresentation {
+	return middlewareRepresentation{
+		MiddlewareInfo: mi,
+		Name:           name,
+		Provider:       getProviderName(name),
+		Type:           strings.ToLower(extractType(mi.Middleware)),
+	}
 }
 
 func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
 	results := make([]routerRepresentation, 0, len(h.runtimeConfiguration.Routers))
 
+	criterion := newSearchCriterion(request.URL.Query())
+
 	for name, rt := range h.runtimeConfiguration.Routers {
-		results = append(results, routerRepresentation{
-			RouterInfo: rt,
-			Name:       name,
-			Provider:   getProviderName(name),
-		})
+		if keepRouter(name, rt, criterion) {
+			results = append(results, newRouterRepresentation(name, rt))
+		}
 	}
 
 	sort.Slice(results, func(i, j int) bool {
 		return results[i].Name < results[j].Name
 	})
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	pageInfo, err := pagination(request, len(results))
 	if err != nil {
-		http.Error(rw, err.Error(), http.StatusBadRequest)
+		writeError(rw, err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
 	rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
 
 	err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
 	routerID := mux.Vars(request)["routerID"]
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	router, ok := h.runtimeConfiguration.Routers[routerID]
 	if !ok {
-		http.NotFound(rw, request)
+		writeError(rw, fmt.Sprintf("router not found: %s", routerID), http.StatusNotFound)
 		return
 	}
 
-	result := routerRepresentation{
-		RouterInfo: router,
-		Name:       routerID,
-		Provider:   getProviderName(routerID),
-	}
-
-	rw.Header().Set("Content-Type", "application/json")
+	result := newRouterRepresentation(routerID, router)
 
 	err := json.NewEncoder(rw).Encode(result)
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
 	results := make([]serviceRepresentation, 0, len(h.runtimeConfiguration.Services))
 
+	criterion := newSearchCriterion(request.URL.Query())
+
 	for name, si := range h.runtimeConfiguration.Services {
-		results = append(results, serviceRepresentation{
-			ServiceInfo:  si,
-			Name:         name,
-			Provider:     getProviderName(name),
-			ServerStatus: si.GetAllStatus(),
-		})
+		if keepService(name, si, criterion) {
+			results = append(results, newServiceRepresentation(name, si))
+		}
 	}
 
 	sort.Slice(results, func(i, j int) bool {
 		return results[i].Name < results[j].Name
 	})
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	pageInfo, err := pagination(request, len(results))
 	if err != nil {
-		http.Error(rw, err.Error(), http.StatusBadRequest)
+		writeError(rw, err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
 	rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
 
 	err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
 	serviceID := mux.Vars(request)["serviceID"]
 
+	rw.Header().Add("Content-Type", "application/json")
+
 	service, ok := h.runtimeConfiguration.Services[serviceID]
 	if !ok {
-		http.NotFound(rw, request)
+		writeError(rw, fmt.Sprintf("service not found: %s", serviceID), http.StatusNotFound)
 		return
 	}
 
-	result := serviceRepresentation{
-		ServiceInfo:  service,
-		Name:         serviceID,
-		Provider:     getProviderName(serviceID),
-		ServerStatus: service.GetAllStatus(),
-	}
-
-	rw.Header().Add("Content-Type", "application/json")
+	result := newServiceRepresentation(serviceID, service)
 
 	err := json.NewEncoder(rw).Encode(result)
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
 	results := make([]middlewareRepresentation, 0, len(h.runtimeConfiguration.Middlewares))
 
+	criterion := newSearchCriterion(request.URL.Query())
+
 	for name, mi := range h.runtimeConfiguration.Middlewares {
-		results = append(results, middlewareRepresentation{
-			MiddlewareInfo: mi,
-			Name:           name,
-			Provider:       getProviderName(name),
-		})
+		if keepMiddleware(name, mi, criterion) {
+			results = append(results, newMiddlewareRepresentation(name, mi))
+		}
 	}
 
 	sort.Slice(results, func(i, j int) bool {
 		return results[i].Name < results[j].Name
 	})
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	pageInfo, err := pagination(request, len(results))
 	if err != nil {
-		http.Error(rw, err.Error(), http.StatusBadRequest)
+		writeError(rw, err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
 	rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
 
 	err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
 	middlewareID := mux.Vars(request)["middlewareID"]
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	middleware, ok := h.runtimeConfiguration.Middlewares[middlewareID]
 	if !ok {
-		http.NotFound(rw, request)
+		writeError(rw, fmt.Sprintf("middleware not found: %s", middlewareID), http.StatusNotFound)
 		return
 	}
 
-	result := middlewareRepresentation{
-		MiddlewareInfo: middleware,
-		Name:           middlewareID,
-		Provider:       getProviderName(middlewareID),
-	}
-
-	rw.Header().Set("Content-Type", "application/json")
+	result := newMiddlewareRepresentation(middlewareID, middleware)
 
 	err := json.NewEncoder(rw).Encode(result)
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
+
+func keepRouter(name string, item *runtime.RouterInfo, criterion *searchCriterion) bool {
+	if criterion == nil {
+		return true
+	}
+
+	return criterion.withStatus(item.Status) && criterion.searchIn(item.Rule, name)
+}
+
+func keepService(name string, item *runtime.ServiceInfo, criterion *searchCriterion) bool {
+	if criterion == nil {
+		return true
+	}
+
+	return criterion.withStatus(item.Status) && criterion.searchIn(name)
+}
+
+func keepMiddleware(name string, item *runtime.MiddlewareInfo, criterion *searchCriterion) bool {
+	if criterion == nil {
+		return true
+	}
+
+	return criterion.withStatus(item.Status) && criterion.searchIn(name)
+}
diff --git a/pkg/api/handler_overview.go b/pkg/api/handler_overview.go
index f08d80351..21f1921e1 100644
--- a/pkg/api/handler_overview.go
+++ b/pkg/api/handler_overview.go
@@ -56,7 +56,7 @@ func (h Handler) getOverview(rw http.ResponseWriter, request *http.Request) {
 	err := json.NewEncoder(rw).Encode(result)
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
diff --git a/pkg/api/handler_tcp.go b/pkg/api/handler_tcp.go
index 3f91f0386..823af5178 100644
--- a/pkg/api/handler_tcp.go
+++ b/pkg/api/handler_tcp.go
@@ -2,9 +2,11 @@ package api
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"sort"
 	"strconv"
+	"strings"
 
 	"github.com/containous/traefik/v2/pkg/config/runtime"
 	"github.com/containous/traefik/v2/pkg/log"
@@ -17,118 +19,146 @@ type tcpRouterRepresentation struct {
 	Provider string `json:"provider,omitempty"`
 }
 
+func newTCPRouterRepresentation(name string, rt *runtime.TCPRouterInfo) tcpRouterRepresentation {
+	return tcpRouterRepresentation{
+		TCPRouterInfo: rt,
+		Name:          name,
+		Provider:      getProviderName(name),
+	}
+}
+
 type tcpServiceRepresentation struct {
 	*runtime.TCPServiceInfo
 	Name     string `json:"name,omitempty"`
 	Provider string `json:"provider,omitempty"`
+	Type     string `json:"type,omitempty"`
+}
+
+func newTCPServiceRepresentation(name string, si *runtime.TCPServiceInfo) tcpServiceRepresentation {
+	return tcpServiceRepresentation{
+		TCPServiceInfo: si,
+		Name:           name,
+		Provider:       getProviderName(name),
+		Type:           strings.ToLower(extractType(si.TCPService)),
+	}
 }
 
 func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
 	results := make([]tcpRouterRepresentation, 0, len(h.runtimeConfiguration.TCPRouters))
 
+	criterion := newSearchCriterion(request.URL.Query())
+
 	for name, rt := range h.runtimeConfiguration.TCPRouters {
-		results = append(results, tcpRouterRepresentation{
-			TCPRouterInfo: rt,
-			Name:          name,
-			Provider:      getProviderName(name),
-		})
+		if keepTCPRouter(name, rt, criterion) {
+			results = append(results, newTCPRouterRepresentation(name, rt))
+		}
 	}
 
 	sort.Slice(results, func(i, j int) bool {
 		return results[i].Name < results[j].Name
 	})
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	pageInfo, err := pagination(request, len(results))
 	if err != nil {
-		http.Error(rw, err.Error(), http.StatusBadRequest)
+		writeError(rw, err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
 	rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
 
 	err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
 	routerID := mux.Vars(request)["routerID"]
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	router, ok := h.runtimeConfiguration.TCPRouters[routerID]
 	if !ok {
-		http.NotFound(rw, request)
+		writeError(rw, fmt.Sprintf("router not found: %s", routerID), http.StatusNotFound)
 		return
 	}
 
-	result := tcpRouterRepresentation{
-		TCPRouterInfo: router,
-		Name:          routerID,
-		Provider:      getProviderName(routerID),
-	}
-
-	rw.Header().Set("Content-Type", "application/json")
+	result := newTCPRouterRepresentation(routerID, router)
 
 	err := json.NewEncoder(rw).Encode(result)
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
 	results := make([]tcpServiceRepresentation, 0, len(h.runtimeConfiguration.TCPServices))
 
+	criterion := newSearchCriterion(request.URL.Query())
+
 	for name, si := range h.runtimeConfiguration.TCPServices {
-		results = append(results, tcpServiceRepresentation{
-			TCPServiceInfo: si,
-			Name:           name,
-			Provider:       getProviderName(name),
-		})
+		if keepTCPService(name, si, criterion) {
+			results = append(results, newTCPServiceRepresentation(name, si))
+		}
 	}
 
 	sort.Slice(results, func(i, j int) bool {
 		return results[i].Name < results[j].Name
 	})
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	pageInfo, err := pagination(request, len(results))
 	if err != nil {
-		http.Error(rw, err.Error(), http.StatusBadRequest)
+		writeError(rw, err.Error(), http.StatusBadRequest)
 		return
 	}
 
-	rw.Header().Set("Content-Type", "application/json")
 	rw.Header().Set(nextPageHeader, strconv.Itoa(pageInfo.nextPage))
 
 	err = json.NewEncoder(rw).Encode(results[pageInfo.startIndex:pageInfo.endIndex])
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
 
 func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
 	serviceID := mux.Vars(request)["serviceID"]
 
+	rw.Header().Set("Content-Type", "application/json")
+
 	service, ok := h.runtimeConfiguration.TCPServices[serviceID]
 	if !ok {
-		http.NotFound(rw, request)
+		writeError(rw, fmt.Sprintf("service not found: %s", serviceID), http.StatusNotFound)
 		return
 	}
 
-	result := tcpServiceRepresentation{
-		TCPServiceInfo: service,
-		Name:           serviceID,
-		Provider:       getProviderName(serviceID),
-	}
-
-	rw.Header().Set("Content-Type", "application/json")
+	result := newTCPServiceRepresentation(serviceID, service)
 
 	err := json.NewEncoder(rw).Encode(result)
 	if err != nil {
 		log.FromContext(request.Context()).Error(err)
-		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		writeError(rw, err.Error(), http.StatusInternalServerError)
 	}
 }
+
+func keepTCPRouter(name string, item *runtime.TCPRouterInfo, criterion *searchCriterion) bool {
+	if criterion == nil {
+		return true
+	}
+
+	return criterion.withStatus(item.Status) && criterion.searchIn(item.Rule, name)
+}
+
+func keepTCPService(name string, item *runtime.TCPServiceInfo, criterion *searchCriterion) bool {
+	if criterion == nil {
+		return true
+	}
+
+	return criterion.withStatus(item.Status) && criterion.searchIn(name)
+}
diff --git a/pkg/config/runtime/runtime_http.go b/pkg/config/runtime/runtime_http.go
index e604ac32c..af5062fd9 100644
--- a/pkg/config/runtime/runtime_http.go
+++ b/pkg/config/runtime/runtime_http.go
@@ -3,13 +3,14 @@ package runtime
 import (
 	"context"
 	"fmt"
+	"sort"
 	"sync"
 
 	"github.com/containous/traefik/v2/pkg/config/dynamic"
 	"github.com/containous/traefik/v2/pkg/log"
 )
 
-// GetRoutersByEntryPoints returns all the http routers by entry points name and routers name
+// GetRoutersByEntryPoints returns all the http routers by entry points name and routers name.
 func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints []string, tls bool) map[string]map[string]*RouterInfo {
 	entryPointsRouters := make(map[string]map[string]*RouterInfo)
 
@@ -19,11 +20,13 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints
 		}
 
 		logger := log.FromContext(log.With(ctx, log.Str(log.RouterName, rtName)))
+
 		eps := rt.EntryPoints
 		if len(eps) == 0 {
-			logger.Debugf("No entrypoint defined for this router, using the default one(s) instead: %+v", entryPoints)
+			logger.Debugf("No entryPoint defined for this router, using the default one(s) instead: %+v", entryPoints)
 			eps = entryPoints
 		}
+
 		entryPointsCount := 0
 		for _, entryPointName := range eps {
 			if !contains(entryPoints, entryPointName) {
@@ -33,23 +36,44 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints
 				continue
 			}
 
-			entryPointsCount++
 			if _, ok := entryPointsRouters[entryPointName]; !ok {
 				entryPointsRouters[entryPointName] = make(map[string]*RouterInfo)
 			}
 
+			entryPointsCount++
+			rt.Using = append(rt.Using, entryPointName)
+
 			entryPointsRouters[entryPointName][rtName] = rt
 		}
+
 		if entryPointsCount == 0 {
 			rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true)
 			logger.Error("no valid entryPoint for this router")
 		}
+
+		rt.Using = unique(rt.Using)
 	}
 
 	return entryPointsRouters
 }
 
-// RouterInfo holds information about a currently running HTTP router
+func unique(src []string) []string {
+	var uniq []string
+
+	set := make(map[string]struct{})
+	for _, v := range src {
+		if _, exist := set[v]; !exist {
+			set[v] = struct{}{}
+			uniq = append(uniq, v)
+		}
+	}
+
+	sort.Strings(uniq)
+
+	return uniq
+}
+
+// RouterInfo holds information about a currently running HTTP router.
 type RouterInfo struct {
 	*dynamic.Router // dynamic configuration
 	// Err contains all the errors that occurred during router's creation.
@@ -57,7 +81,8 @@ type RouterInfo struct {
 	// Status reports whether the router is disabled, in a warning state, or all good (enabled).
 	// If not in "enabled" state, the reason for it should be in the list of Err.
 	// It is the caller's responsibility to set the initial status.
-	Status string `json:"status,omitempty"`
+	Status string   `json:"status,omitempty"`
+	Using  []string `json:"using,omitempty"` // Effective entry points used by that router.
 }
 
 // AddError adds err to r.Err, if it does not already exist.
@@ -81,13 +106,13 @@ func (r *RouterInfo) AddError(err error, critical bool) {
 	}
 }
 
-// MiddlewareInfo holds information about a currently running middleware
+// MiddlewareInfo holds information about a currently running middleware.
 type MiddlewareInfo struct {
 	*dynamic.Middleware // dynamic configuration
 	// Err contains all the errors that occurred during service creation.
 	Err    []string `json:"error,omitempty"`
 	Status string   `json:"status,omitempty"`
-	UsedBy []string `json:"usedBy,omitempty"` // list of routers and services using that middleware
+	UsedBy []string `json:"usedBy,omitempty"` // list of routers and services using that middleware.
 }
 
 // AddError adds err to s.Err, if it does not already exist.
@@ -111,7 +136,7 @@ func (m *MiddlewareInfo) AddError(err error, critical bool) {
 	}
 }
 
-// ServiceInfo holds information about a currently running service
+// ServiceInfo holds information about a currently running service.
 type ServiceInfo struct {
 	*dynamic.Service // dynamic configuration
 	// Err contains all the errors that occurred during service creation.
diff --git a/pkg/config/runtime/runtime_tcp.go b/pkg/config/runtime/runtime_tcp.go
index a99d519f9..427412bdb 100644
--- a/pkg/config/runtime/runtime_tcp.go
+++ b/pkg/config/runtime/runtime_tcp.go
@@ -2,24 +2,30 @@ package runtime
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/containous/traefik/v2/pkg/config/dynamic"
 	"github.com/containous/traefik/v2/pkg/log"
 )
 
-// GetTCPRoutersByEntryPoints returns all the tcp routers by entry points name and routers name
+// GetTCPRoutersByEntryPoints returns all the tcp routers by entry points name and routers name.
 func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoints []string) map[string]map[string]*TCPRouterInfo {
 	entryPointsRouters := make(map[string]map[string]*TCPRouterInfo)
 
 	for rtName, rt := range c.TCPRouters {
+		logger := log.FromContext(log.With(ctx, log.Str(log.RouterName, rtName)))
+
 		eps := rt.EntryPoints
 		if len(eps) == 0 {
+			logger.Debugf("No entryPoint defined for this router, using the default one(s) instead: %+v", entryPoints)
 			eps = entryPoints
 		}
 
+		entryPointsCount := 0
 		for _, entryPointName := range eps {
 			if !contains(entryPoints, entryPointName) {
-				log.FromContext(log.With(ctx, log.Str(log.EntryPointName, entryPointName))).
+				rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false)
+				logger.WithField(log.EntryPointName, entryPointName).
 					Errorf("entryPoint %q doesn't exist", entryPointName)
 				continue
 			}
@@ -28,21 +34,30 @@ func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoi
 				entryPointsRouters[entryPointName] = make(map[string]*TCPRouterInfo)
 			}
 
+			entryPointsCount++
+			rt.Using = append(rt.Using, entryPointName)
+
 			entryPointsRouters[entryPointName][rtName] = rt
 		}
+
+		if entryPointsCount == 0 {
+			rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true)
+			logger.Error("no valid entryPoint for this router")
+		}
 	}
 
 	return entryPointsRouters
 }
 
-// TCPRouterInfo holds information about a currently running TCP router
+// TCPRouterInfo holds information about a currently running TCP router.
 type TCPRouterInfo struct {
 	*dynamic.TCPRouter          // dynamic configuration
 	Err                []string `json:"error,omitempty"` // initialization error
 	// Status reports whether the router is disabled, in a warning state, or all good (enabled).
 	// If not in "enabled" state, the reason for it should be in the list of Err.
 	// It is the caller's responsibility to set the initial status.
-	Status string `json:"status,omitempty"`
+	Status string   `json:"status,omitempty"`
+	Using  []string `json:"using,omitempty"` // Effective entry points used by that router.
 }
 
 // AddError adds err to r.Err, if it does not already exist.
@@ -66,7 +81,7 @@ func (r *TCPRouterInfo) AddError(err error, critical bool) {
 	}
 }
 
-// TCPServiceInfo holds information about a currently running TCP service
+// TCPServiceInfo holds information about a currently running TCP service.
 type TCPServiceInfo struct {
 	*dynamic.TCPService          // dynamic configuration
 	Err                 []string `json:"error,omitempty"` // initialization error
diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go
index 52d18dd30..059e41b9c 100644
--- a/pkg/provider/kubernetes/crd/kubernetes_http.go
+++ b/pkg/provider/kubernetes/crd/kubernetes_http.go
@@ -144,7 +144,6 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
 				}
 				conf.Routers[serviceName].TLS = tlsConf
 			}
-
 		}
 	}
 


Test patch:
diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json
index c26803e36..696e34dd2 100644
--- a/integration/testdata/rawdata-crd.json
+++ b/integration/testdata/rawdata-crd.json
@@ -10,7 +10,10 @@
 			"tls": {
 				"options": "default/mytlsoption"
 			},
-			"status": "enabled"
+			"status": "enabled",
+			"using": [
+				"web"
+			]
 		},
 		"default/test2.route-23c7f4c450289ee29016@kubernetescrd": {
 			"entryPoints": [
@@ -21,7 +24,10 @@
 			],
 			"service": "default/test2.route-23c7f4c450289ee29016",
 			"rule": "Host(`foo.com`) \u0026\u0026 PathPrefix(`/tobestripped`)",
-			"status": "enabled"
+			"status": "enabled",
+			"using": [
+				"web"
+			]
 		}
 	},
 	"middlewares": {
@@ -42,10 +48,10 @@
 			"loadBalancer": {
 				"servers": [
 					{
-						"url": "http://10.42.0.4:80"
+						"url": "http://10.42.0.2:80"
 					},
 					{
-						"url": "http://10.42.0.5:80"
+						"url": "http://10.42.0.6:80"
 					}
 				],
 				"passHostHeader": true
@@ -55,18 +61,18 @@
 				"default/test.route-6b204d94623b3df4370c@kubernetescrd"
 			],
 			"serverStatus": {
-				"http://10.42.0.4:80": "UP",
-				"http://10.42.0.5:80": "UP"
+				"http://10.42.0.2:80": "UP",
+				"http://10.42.0.6:80": "UP"
 			}
 		},
 		"default/test2.route-23c7f4c450289ee29016@kubernetescrd": {
 			"loadBalancer": {
 				"servers": [
 					{
-						"url": "http://10.42.0.4:80"
+						"url": "http://10.42.0.2:80"
 					},
 					{
-						"url": "http://10.42.0.5:80"
+						"url": "http://10.42.0.6:80"
 					}
 				],
 				"passHostHeader": true
@@ -76,8 +82,8 @@
 				"default/test2.route-23c7f4c450289ee29016@kubernetescrd"
 			],
 			"serverStatus": {
-				"http://10.42.0.4:80": "UP",
-				"http://10.42.0.5:80": "UP"
+				"http://10.42.0.2:80": "UP",
+				"http://10.42.0.6:80": "UP"
 			}
 		}
 	},
@@ -92,7 +98,10 @@
 				"passthrough": false,
 				"options": "default/mytlsoption"
 			},
-			"status": "enabled"
+			"status": "enabled",
+			"using": [
+				"footcp"
+			]
 		}
 	},
 	"tcpServices": {
@@ -100,10 +109,10 @@
 			"loadBalancer": {
 				"servers": [
 					{
-						"address": "10.42.0.3:8080"
+						"address": "10.42.0.4:8080"
 					},
 					{
-						"address": "10.42.0.6:8080"
+						"address": "10.42.0.5:8080"
 					}
 				]
 			},
diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json
index 9a9e1b9d7..4908d6388 100644
--- a/integration/testdata/rawdata-ingress.json
+++ b/integration/testdata/rawdata-ingress.json
@@ -4,17 +4,29 @@
 			"service": "default/whoami/http",
 			"rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)",
 			"tls": {},
-			"status": "enabled"
+			"status": "enabled",
+			"using": [
+				"traefik",
+				"web"
+			]
 		},
 		"whoami-test-https/whoami@kubernetes": {
 			"service": "default/whoami/http",
 			"rule": "Host(`whoami.test.https`) \u0026\u0026 PathPrefix(`/whoami`)",
-			"status": "enabled"
+			"status": "enabled",
+			"using": [
+				"traefik",
+				"web"
+			]
 		},
 		"whoami-test/whoami@kubernetes": {
 			"service": "default/whoami/http",
 			"rule": "Host(`whoami.test`) \u0026\u0026 PathPrefix(`/whoami`)",
-			"status": "enabled"
+			"status": "enabled",
+			"using": [
+				"traefik",
+				"web"
+			]
 		}
 	},
 	"services": {
@@ -22,10 +34,10 @@
 			"loadBalancer": {
 				"servers": [
 					{
-						"url": "http://10.42.0.4:80"
+						"url": "http://10.42.0.2:80"
 					},
 					{
-						"url": "http://10.42.0.5:80"
+						"url": "http://10.42.0.4:80"
 					}
 				],
 				"passHostHeader": true
@@ -37,8 +49,8 @@
 				"whoami-test/whoami@kubernetes"
 			],
 			"serverStatus": {
-				"http://10.42.0.4:80": "UP",
-				"http://10.42.0.5:80": "UP"
+				"http://10.42.0.2:80": "UP",
+				"http://10.42.0.4:80": "UP"
 			}
 		}
 	}
diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go
index 57524e675..38286151f 100644
--- a/pkg/api/handler_http_test.go
+++ b/pkg/api/handler_http_test.go
@@ -1,6 +1,7 @@
 package api
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
@@ -137,6 +138,68 @@ func TestHandler_HTTP(t *testing.T) {
 				statusCode: http.StatusBadRequest,
 			},
 		},
+		{
+			desc: "routers filtered by status",
+			path: "/api/http/routers?status=enabled",
+			conf: runtime.Configuration{
+				Routers: map[string]*runtime.RouterInfo{
+					"test@myprovider": {
+						Router: &dynamic.Router{
+							EntryPoints: []string{"web"},
+							Service:     "foo-service@myprovider",
+							Rule:        "Host(`foo.bar.other`)",
+							Middlewares: []string{"addPrefixTest", "auth"},
+						},
+						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-status.json",
+			},
+		},
+		{
+			desc: "routers filtered by search",
+			path: "/api/http/routers?search=fii",
+			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,
+					},
+					"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-search.json",
+			},
+		},
 		{
 			desc: "one router by id",
 			path: "/api/http/routers/bar@myprovider",
@@ -232,6 +295,45 @@ func TestHandler_HTTP(t *testing.T) {
 						si.UpdateServerStatus("http://127.0.0.2", "UP")
 						return si
 					}(),
+					"canary@myprovider": {
+						Service: &dynamic.Service{
+							Weighted: &dynamic.WeightedRoundRobin{
+								Services: nil,
+								Sticky: &dynamic.Sticky{
+									Cookie: &dynamic.Cookie{
+										Name:     "chocolat",
+										Secure:   true,
+										HTTPOnly: true,
+									},
+								},
+							},
+						},
+						Status: runtime.StatusEnabled,
+						UsedBy: []string{"foo@myprovider"},
+					},
+					"mirror@myprovider": {
+						Service: &dynamic.Service{
+							Mirroring: &dynamic.Mirroring{
+								Service: "one@myprovider",
+								Mirrors: []dynamic.MirrorService{
+									{
+										Name:    "two@myprovider",
+										Percent: 10,
+									},
+									{
+										Name:    "three@myprovider",
+										Percent: 15,
+									},
+									{
+										Name:    "four@myprovider",
+										Percent: 80,
+									},
+								},
+							},
+						},
+						Status: runtime.StatusEnabled,
+						UsedBy: []string{"foo@myprovider"},
+					},
 				},
 			},
 			expected: expected{
@@ -301,6 +403,100 @@ func TestHandler_HTTP(t *testing.T) {
 				jsonFile:   "testdata/services-page2.json",
 			},
 		},
+		{
+			desc: "services filtered by status",
+			path: "/api/http/services?status=enabled",
+			conf: runtime.Configuration{
+				Services: map[string]*runtime.ServiceInfo{
+					"bar@myprovider": func() *runtime.ServiceInfo {
+						si := &runtime.ServiceInfo{
+							Service: &dynamic.Service{
+								LoadBalancer: &dynamic.ServersLoadBalancer{
+									Servers: []dynamic.Server{
+										{
+											URL: "http://127.0.0.1",
+										},
+									},
+								},
+							},
+							UsedBy: []string{"foo@myprovider", "test@myprovider"},
+							Status: runtime.StatusEnabled,
+						}
+						si.UpdateServerStatus("http://127.0.0.1", "UP")
+						return si
+					}(),
+					"baz@myprovider": func() *runtime.ServiceInfo {
+						si := &runtime.ServiceInfo{
+							Service: &dynamic.Service{
+								LoadBalancer: &dynamic.ServersLoadBalancer{
+									Servers: []dynamic.Server{
+										{
+											URL: "http://127.0.0.2",
+										},
+									},
+								},
+							},
+							UsedBy: []string{"foo@myprovider"},
+							Status: runtime.StatusDisabled,
+						}
+						si.UpdateServerStatus("http://127.0.0.2", "UP")
+						return si
+					}(),
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/services-filtered-status.json",
+			},
+		},
+		{
+			desc: "services filtered by search",
+			path: "/api/http/services?search=baz",
+			conf: runtime.Configuration{
+				Services: map[string]*runtime.ServiceInfo{
+					"bar@myprovider": func() *runtime.ServiceInfo {
+						si := &runtime.ServiceInfo{
+							Service: &dynamic.Service{
+								LoadBalancer: &dynamic.ServersLoadBalancer{
+									Servers: []dynamic.Server{
+										{
+											URL: "http://127.0.0.1",
+										},
+									},
+								},
+							},
+							UsedBy: []string{"foo@myprovider", "test@myprovider"},
+							Status: runtime.StatusEnabled,
+						}
+						si.UpdateServerStatus("http://127.0.0.1", "UP")
+						return si
+					}(),
+					"baz@myprovider": func() *runtime.ServiceInfo {
+						si := &runtime.ServiceInfo{
+							Service: &dynamic.Service{
+								LoadBalancer: &dynamic.ServersLoadBalancer{
+									Servers: []dynamic.Server{
+										{
+											URL: "http://127.0.0.2",
+										},
+									},
+								},
+							},
+							UsedBy: []string{"foo@myprovider"},
+							Status: runtime.StatusDisabled,
+						}
+						si.UpdateServerStatus("http://127.0.0.2", "UP")
+						return si
+					}(),
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/services-filtered-search.json",
+			},
+		},
 		{
 			desc: "one service by id",
 			path: "/api/http/services/bar@myprovider",
@@ -411,6 +607,86 @@ func TestHandler_HTTP(t *testing.T) {
 				jsonFile:   "testdata/middlewares.json",
 			},
 		},
+		{
+			desc: "middlewares filtered by status",
+			path: "/api/http/middlewares?status=enabled",
+			conf: runtime.Configuration{
+				Middlewares: map[string]*runtime.MiddlewareInfo{
+					"auth@myprovider": {
+						Middleware: &dynamic.Middleware{
+							BasicAuth: &dynamic.BasicAuth{
+								Users: []string{"admin:admin"},
+							},
+						},
+						UsedBy: []string{"bar@myprovider", "test@myprovider"},
+						Status: runtime.StatusEnabled,
+					},
+					"addPrefixTest@myprovider": {
+						Middleware: &dynamic.Middleware{
+							AddPrefix: &dynamic.AddPrefix{
+								Prefix: "/titi",
+							},
+						},
+						UsedBy: []string{"test@myprovider"},
+						Status: runtime.StatusDisabled,
+					},
+					"addPrefixTest@anotherprovider": {
+						Middleware: &dynamic.Middleware{
+							AddPrefix: &dynamic.AddPrefix{
+								Prefix: "/toto",
+							},
+						},
+						UsedBy: []string{"bar@myprovider"},
+						Status: runtime.StatusEnabled,
+					},
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/middlewares-filtered-status.json",
+			},
+		},
+		{
+			desc: "middlewares filtered by search",
+			path: "/api/http/middlewares?search=addprefixtest",
+			conf: runtime.Configuration{
+				Middlewares: map[string]*runtime.MiddlewareInfo{
+					"auth@myprovider": {
+						Middleware: &dynamic.Middleware{
+							BasicAuth: &dynamic.BasicAuth{
+								Users: []string{"admin:admin"},
+							},
+						},
+						UsedBy: []string{"bar@myprovider", "test@myprovider"},
+						Status: runtime.StatusEnabled,
+					},
+					"addPrefixTest@myprovider": {
+						Middleware: &dynamic.Middleware{
+							AddPrefix: &dynamic.AddPrefix{
+								Prefix: "/titi",
+							},
+						},
+						UsedBy: []string{"test@myprovider"},
+						Status: runtime.StatusDisabled,
+					},
+					"addPrefixTest@anotherprovider": {
+						Middleware: &dynamic.Middleware{
+							AddPrefix: &dynamic.AddPrefix{
+								Prefix: "/toto",
+							},
+						},
+						UsedBy: []string{"bar@myprovider"},
+						Status: runtime.StatusEnabled,
+					},
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/middlewares-filtered-search.json",
+			},
+		},
 		{
 			desc: "all middlewares, 1 res per page, want page 2",
 			path: "/api/http/middlewares?page=2&per_page=1",
@@ -521,6 +797,8 @@ func TestHandler_HTTP(t *testing.T) {
 			rtConf := &test.conf
 			// To lazily initialize the Statuses.
 			rtConf.PopulateUsedBy()
+			rtConf.GetRoutersByEntryPoints(context.Background(), []string{"web"}, false)
+
 			handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
 			router := mux.NewRouter()
 			handler.Append(router)
diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go
index 28c833f8b..798876de4 100644
--- a/pkg/api/handler_tcp_test.go
+++ b/pkg/api/handler_tcp_test.go
@@ -1,6 +1,7 @@
 package api
 
 import (
+	"context"
 	"encoding/json"
 	"io/ioutil"
 	"net/http"
@@ -112,6 +113,86 @@ func TestHandler_TCP(t *testing.T) {
 				jsonFile:   "testdata/tcprouters-page2.json",
 			},
 		},
+		{
+			desc: "TCP routers filtered by status",
+			path: "/api/tcp/routers?status=enabled",
+			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@myprovider",
+							Rule:        "Host(`foo.bar`)",
+						},
+						Status: runtime.StatusWarning,
+					},
+					"foo@myprovider": {
+						TCPRouter: &dynamic.TCPRouter{
+							EntryPoints: []string{"web"},
+							Service:     "foo-service@myprovider",
+							Rule:        "Host(`foo.bar`)",
+						},
+						Status: runtime.StatusDisabled,
+					},
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/tcprouters-filtered-status.json",
+			},
+		},
+		{
+			desc: "TCP routers filtered by search",
+			path: "/api/tcp/routers?search=bar@my",
+			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@myprovider",
+							Rule:        "Host(`foo.bar`)",
+						},
+						Status: runtime.StatusWarning,
+					},
+					"foo@myprovider": {
+						TCPRouter: &dynamic.TCPRouter{
+							EntryPoints: []string{"web"},
+							Service:     "foo-service@myprovider",
+							Rule:        "Host(`foo.bar`)",
+						},
+						Status: runtime.StatusDisabled,
+					},
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/tcprouters-filtered-search.json",
+			},
+		},
 		{
 			desc: "one TCP router by id",
 			path: "/api/tcp/routers/bar@myprovider",
@@ -219,6 +300,110 @@ func TestHandler_TCP(t *testing.T) {
 				jsonFile:   "testdata/tcpservices.json",
 			},
 		},
+		{
+			desc: "tcp services filtered by status",
+			path: "/api/tcp/services?status=enabled",
+			conf: runtime.Configuration{
+				TCPServices: map[string]*runtime.TCPServiceInfo{
+					"bar@myprovider": {
+						TCPService: &dynamic.TCPService{
+							LoadBalancer: &dynamic.TCPLoadBalancerService{
+								Servers: []dynamic.TCPServer{
+									{
+										Address: "127.0.0.1:2345",
+									},
+								},
+							},
+						},
+						UsedBy: []string{"foo@myprovider", "test@myprovider"},
+						Status: runtime.StatusEnabled,
+					},
+					"baz@myprovider": {
+						TCPService: &dynamic.TCPService{
+							LoadBalancer: &dynamic.TCPLoadBalancerService{
+								Servers: []dynamic.TCPServer{
+									{
+										Address: "127.0.0.2:2345",
+									},
+								},
+							},
+						},
+						UsedBy: []string{"foo@myprovider"},
+						Status: runtime.StatusWarning,
+					},
+					"foz@myprovider": {
+						TCPService: &dynamic.TCPService{
+							LoadBalancer: &dynamic.TCPLoadBalancerService{
+								Servers: []dynamic.TCPServer{
+									{
+										Address: "127.0.0.2:2345",
+									},
+								},
+							},
+						},
+						UsedBy: []string{"foo@myprovider"},
+						Status: runtime.StatusDisabled,
+					},
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/tcpservices-filtered-status.json",
+			},
+		},
+		{
+			desc: "tcp services filtered by search",
+			path: "/api/tcp/services?search=baz@my",
+			conf: runtime.Configuration{
+				TCPServices: map[string]*runtime.TCPServiceInfo{
+					"bar@myprovider": {
+						TCPService: &dynamic.TCPService{
+							LoadBalancer: &dynamic.TCPLoadBalancerService{
+								Servers: []dynamic.TCPServer{
+									{
+										Address: "127.0.0.1:2345",
+									},
+								},
+							},
+						},
+						UsedBy: []string{"foo@myprovider", "test@myprovider"},
+						Status: runtime.StatusEnabled,
+					},
+					"baz@myprovider": {
+						TCPService: &dynamic.TCPService{
+							LoadBalancer: &dynamic.TCPLoadBalancerService{
+								Servers: []dynamic.TCPServer{
+									{
+										Address: "127.0.0.2:2345",
+									},
+								},
+							},
+						},
+						UsedBy: []string{"foo@myprovider"},
+						Status: runtime.StatusWarning,
+					},
+					"foz@myprovider": {
+						TCPService: &dynamic.TCPService{
+							LoadBalancer: &dynamic.TCPLoadBalancerService{
+								Servers: []dynamic.TCPServer{
+									{
+										Address: "127.0.0.2:2345",
+									},
+								},
+							},
+						},
+						UsedBy: []string{"foo@myprovider"},
+						Status: runtime.StatusDisabled,
+					},
+				},
+			},
+			expected: expected{
+				statusCode: http.StatusOK,
+				nextPage:   "1",
+				jsonFile:   "testdata/tcpservices-filtered-search.json",
+			},
+		},
 		{
 			desc: "all tcp services, 1 res per page, want page 2",
 			path: "/api/tcp/services?page=2&per_page=1",
@@ -330,6 +515,10 @@ func TestHandler_TCP(t *testing.T) {
 			t.Parallel()
 
 			rtConf := &test.conf
+			// To lazily initialize the Statuses.
+			rtConf.PopulateUsedBy()
+			rtConf.GetTCPRoutersByEntryPoints(context.Background(), []string{"web"})
+
 			handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf)
 			router := mux.NewRouter()
 			handler.Append(router)
diff --git a/pkg/api/testdata/middleware-auth.json b/pkg/api/testdata/middleware-auth.json
index 0ed588873..3c7ff53fa 100644
--- a/pkg/api/testdata/middleware-auth.json
+++ b/pkg/api/testdata/middleware-auth.json
@@ -7,6 +7,7 @@
 	"name": "auth@myprovider",
 	"provider": "myprovider",
 	"status": "enabled",
+	"type": "basicauth",
 	"usedBy": [
 		"bar@myprovider",
 		"test@myprovider"
diff --git a/pkg/api/testdata/middlewares-filtered-search.json b/pkg/api/testdata/middlewares-filtered-search.json
new file mode 100644
index 000000000..ba1382268
--- /dev/null
+++ b/pkg/api/testdata/middlewares-filtered-search.json
@@ -0,0 +1,26 @@
+[
+	{
+		"addPrefix": {
+			"prefix": "/toto"
+		},
+		"name": "addPrefixTest@anotherprovider",
+		"provider": "anotherprovider",
+		"status": "enabled",
+		"type": "addprefix",
+		"usedBy": [
+			"bar@myprovider"
+		]
+	},
+	{
+		"addPrefix": {
+			"prefix": "/titi"
+		},
+		"name": "addPrefixTest@myprovider",
+		"provider": "myprovider",
+		"status": "disabled",
+		"type": "addprefix",
+		"usedBy": [
+			"test@myprovider"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/middlewares-filtered-status.json b/pkg/api/testdata/middlewares-filtered-status.json
new file mode 100644
index 000000000..eab60c6a4
--- /dev/null
+++ b/pkg/api/testdata/middlewares-filtered-status.json
@@ -0,0 +1,29 @@
+[
+	{
+		"addPrefix": {
+			"prefix": "/toto"
+		},
+		"name": "addPrefixTest@anotherprovider",
+		"provider": "anotherprovider",
+		"status": "enabled",
+		"type": "addprefix",
+		"usedBy": [
+			"bar@myprovider"
+		]
+	},
+	{
+		"basicAuth": {
+			"users": [
+				"admin:admin"
+			]
+		},
+		"name": "auth@myprovider",
+		"provider": "myprovider",
+		"status": "enabled",
+		"type": "basicauth",
+		"usedBy": [
+			"bar@myprovider",
+			"test@myprovider"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/middlewares-page2.json b/pkg/api/testdata/middlewares-page2.json
index 25ebc13f5..ecaf5978a 100644
--- a/pkg/api/testdata/middlewares-page2.json
+++ b/pkg/api/testdata/middlewares-page2.json
@@ -6,6 +6,7 @@
 		"name": "addPrefixTest@myprovider",
 		"provider": "myprovider",
 		"status": "enabled",
+		"type": "addprefix",
 		"usedBy": [
 			"test@myprovider"
 		]
diff --git a/pkg/api/testdata/middlewares.json b/pkg/api/testdata/middlewares.json
index c27533ee1..f48f52efb 100644
--- a/pkg/api/testdata/middlewares.json
+++ b/pkg/api/testdata/middlewares.json
@@ -6,6 +6,7 @@
 		"name": "addPrefixTest@anotherprovider",
 		"provider": "anotherprovider",
 		"status": "enabled",
+		"type": "addprefix",
 		"usedBy": [
 			"bar@myprovider"
 		]
@@ -17,6 +18,7 @@
 		"name": "addPrefixTest@myprovider",
 		"provider": "myprovider",
 		"status": "enabled",
+		"type": "addprefix",
 		"usedBy": [
 			"test@myprovider"
 		]
@@ -30,6 +32,7 @@
 		"name": "auth@myprovider",
 		"provider": "myprovider",
 		"status": "enabled",
+		"type": "basicauth",
 		"usedBy": [
 			"bar@myprovider",
 			"test@myprovider"
diff --git a/pkg/api/testdata/router-bar.json b/pkg/api/testdata/router-bar.json
index 267a9628b..098ea0b1a 100644
--- a/pkg/api/testdata/router-bar.json
+++ b/pkg/api/testdata/router-bar.json
@@ -10,5 +10,8 @@
 	"provider": "myprovider",
 	"rule": "Host(`foo.bar`)",
 	"service": "foo-service@myprovider",
-	"status": "enabled"
+	"status": "enabled",
+	"using": [
+		"web"
+	]
 }
\ No newline at end of file
diff --git a/pkg/api/testdata/routers-filtered-search.json b/pkg/api/testdata/routers-filtered-search.json
new file mode 100644
index 000000000..4e251f5da
--- /dev/null
+++ b/pkg/api/testdata/routers-filtered-search.json
@@ -0,0 +1,19 @@
+[
+	{
+		"entryPoints": [
+			"web"
+		],
+		"middlewares": [
+			"addPrefixTest",
+			"auth"
+		],
+		"name": "test@myprovider",
+		"provider": "myprovider",
+		"rule": "Host(`fii.bar.other`)",
+		"service": "fii-service@myprovider",
+		"status": "enabled",
+		"using": [
+			"web"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/routers-filtered-status.json b/pkg/api/testdata/routers-filtered-status.json
new file mode 100644
index 000000000..1c96e3802
--- /dev/null
+++ b/pkg/api/testdata/routers-filtered-status.json
@@ -0,0 +1,19 @@
+[
+	{
+		"entryPoints": [
+			"web"
+		],
+		"middlewares": [
+			"addPrefixTest",
+			"auth"
+		],
+		"name": "test@myprovider",
+		"provider": "myprovider",
+		"rule": "Host(`foo.bar.other`)",
+		"service": "foo-service@myprovider",
+		"status": "enabled",
+		"using": [
+			"web"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/routers-many-lastpage.json b/pkg/api/testdata/routers-many-lastpage.json
index 290672375..df1ee7929 100644
--- a/pkg/api/testdata/routers-many-lastpage.json
+++ b/pkg/api/testdata/routers-many-lastpage.json
@@ -7,7 +7,10 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar14`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	},
 	{
 		"entryPoints": [
@@ -17,7 +20,10 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar15`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	},
 	{
 		"entryPoints": [
@@ -27,7 +33,10 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar16`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	},
 	{
 		"entryPoints": [
@@ -37,7 +46,10 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar17`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	},
 	{
 		"entryPoints": [
@@ -47,6 +59,9 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar18`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	}
 ]
\ No newline at end of file
diff --git a/pkg/api/testdata/routers-page2.json b/pkg/api/testdata/routers-page2.json
index 8c7f65d1b..579e2a04f 100644
--- a/pkg/api/testdata/routers-page2.json
+++ b/pkg/api/testdata/routers-page2.json
@@ -7,6 +7,9 @@
 		"provider": "myprovider",
 		"rule": "Host(`toto.bar`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	}
 ]
\ No newline at end of file
diff --git a/pkg/api/testdata/routers.json b/pkg/api/testdata/routers.json
index 664aedc51..ed0d10e28 100644
--- a/pkg/api/testdata/routers.json
+++ b/pkg/api/testdata/routers.json
@@ -11,7 +11,10 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	},
 	{
 		"entryPoints": [
@@ -25,6 +28,9 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar.other`)",
 		"service": "foo-service@myprovider",
-		"status": "enabled"
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	}
 ]
\ No newline at end of file
diff --git a/pkg/api/testdata/service-bar.json b/pkg/api/testdata/service-bar.json
index e26b65a72..11e832136 100644
--- a/pkg/api/testdata/service-bar.json
+++ b/pkg/api/testdata/service-bar.json
@@ -13,6 +13,7 @@
 		"http://127.0.0.1": "UP"
 	},
 	"status": "enabled",
+	"type": "loadbalancer",
 	"usedBy": [
 		"foo@myprovider",
 		"test@myprovider"
diff --git a/pkg/api/testdata/services-filtered-search.json b/pkg/api/testdata/services-filtered-search.json
new file mode 100644
index 000000000..fd854ed72
--- /dev/null
+++ b/pkg/api/testdata/services-filtered-search.json
@@ -0,0 +1,22 @@
+[
+	{
+		"loadBalancer": {
+			"passHostHeader": false,
+			"servers": [
+				{
+					"url": "http://127.0.0.2"
+				}
+			]
+		},
+		"name": "baz@myprovider",
+		"provider": "myprovider",
+		"serverStatus": {
+			"http://127.0.0.2": "UP"
+		},
+		"status": "disabled",
+		"type": "loadbalancer",
+		"usedBy": [
+			"foo@myprovider"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/services-filtered-status.json b/pkg/api/testdata/services-filtered-status.json
new file mode 100644
index 000000000..df1a52dcb
--- /dev/null
+++ b/pkg/api/testdata/services-filtered-status.json
@@ -0,0 +1,23 @@
+[
+	{
+		"loadBalancer": {
+			"passHostHeader": false,
+			"servers": [
+				{
+					"url": "http://127.0.0.1"
+				}
+			]
+		},
+		"name": "bar@myprovider",
+		"provider": "myprovider",
+		"serverStatus": {
+			"http://127.0.0.1": "UP"
+		},
+		"status": "enabled",
+		"type": "loadbalancer",
+		"usedBy": [
+			"foo@myprovider",
+			"test@myprovider"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/services-page2.json b/pkg/api/testdata/services-page2.json
index 66b8390d1..9b8b5ce72 100644
--- a/pkg/api/testdata/services-page2.json
+++ b/pkg/api/testdata/services-page2.json
@@ -14,6 +14,7 @@
 			"http://127.0.0.2": "UP"
 		},
 		"status": "enabled",
+		"type": "loadbalancer",
 		"usedBy": [
 			"foo@myprovider"
 		]
diff --git a/pkg/api/testdata/services.json b/pkg/api/testdata/services.json
index bd54b536c..e96abc23b 100644
--- a/pkg/api/testdata/services.json
+++ b/pkg/api/testdata/services.json
@@ -14,6 +14,7 @@
 			"http://127.0.0.1": "UP"
 		},
 		"status": "enabled",
+		"type": "loadbalancer",
 		"usedBy": [
 			"foo@myprovider",
 			"test@myprovider"
@@ -34,6 +35,51 @@
 			"http://127.0.0.2": "UP"
 		},
 		"status": "enabled",
+		"type": "loadbalancer",
+		"usedBy": [
+			"foo@myprovider"
+		]
+	},
+	{
+		"name": "canary@myprovider",
+		"provider": "myprovider",
+		"status": "enabled",
+		"type": "weighted",
+		"usedBy": [
+			"foo@myprovider"
+		],
+		"weighted": {
+			"sticky": {
+				"cookie": {
+					"httpOnly": true,
+					"name": "chocolat",
+					"secure": true
+				}
+			}
+		}
+	},
+	{
+		"mirroring": {
+			"mirrors": [
+				{
+					"name": "two@myprovider",
+					"percent": 10
+				},
+				{
+					"name": "three@myprovider",
+					"percent": 15
+				},
+				{
+					"name": "four@myprovider",
+					"percent": 80
+				}
+			],
+			"service": "one@myprovider"
+		},
+		"name": "mirror@myprovider",
+		"provider": "myprovider",
+		"status": "enabled",
+		"type": "mirroring",
 		"usedBy": [
 			"foo@myprovider"
 		]
diff --git a/pkg/api/testdata/tcprouter-bar.json b/pkg/api/testdata/tcprouter-bar.json
index b6b244199..70d06a3ea 100644
--- a/pkg/api/testdata/tcprouter-bar.json
+++ b/pkg/api/testdata/tcprouter-bar.json
@@ -5,5 +5,9 @@
 	"name": "bar@myprovider",
 	"provider": "myprovider",
 	"rule": "Host(`foo.bar`)",
-	"service": "foo-service@myprovider"
+	"service": "foo-service@myprovider",
+	"status": "enabled",
+	"using": [
+		"web"
+	]
 }
\ No newline at end of file
diff --git a/pkg/api/testdata/tcprouters-filtered-search.json b/pkg/api/testdata/tcprouters-filtered-search.json
new file mode 100644
index 000000000..4593f1b03
--- /dev/null
+++ b/pkg/api/testdata/tcprouters-filtered-search.json
@@ -0,0 +1,15 @@
+[
+	{
+		"entryPoints": [
+			"web"
+		],
+		"name": "bar@myprovider",
+		"provider": "myprovider",
+		"rule": "Host(`foo.bar`)",
+		"service": "foo-service@myprovider",
+		"status": "warning",
+		"using": [
+			"web"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/tcprouters-filtered-status.json b/pkg/api/testdata/tcprouters-filtered-status.json
new file mode 100644
index 000000000..64d232ebc
--- /dev/null
+++ b/pkg/api/testdata/tcprouters-filtered-status.json
@@ -0,0 +1,18 @@
+[
+	{
+		"entryPoints": [
+			"web"
+		],
+		"name": "test@myprovider",
+		"provider": "myprovider",
+		"rule": "Host(`foo.bar.other`)",
+		"service": "foo-service@myprovider",
+		"status": "enabled",
+		"tls": {
+			"passthrough": false
+		},
+		"using": [
+			"web"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/tcprouters-page2.json b/pkg/api/testdata/tcprouters-page2.json
index 73d97c6d6..579e2a04f 100644
--- a/pkg/api/testdata/tcprouters-page2.json
+++ b/pkg/api/testdata/tcprouters-page2.json
@@ -6,6 +6,10 @@
 		"name": "baz@myprovider",
 		"provider": "myprovider",
 		"rule": "Host(`toto.bar`)",
-		"service": "foo-service@myprovider"
+		"service": "foo-service@myprovider",
+		"status": "enabled",
+		"using": [
+			"web"
+		]
 	}
 ]
\ No newline at end of file
diff --git a/pkg/api/testdata/tcprouters.json b/pkg/api/testdata/tcprouters.json
index a2bf3c401..bf6bffc17 100644
--- a/pkg/api/testdata/tcprouters.json
+++ b/pkg/api/testdata/tcprouters.json
@@ -7,7 +7,10 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar`)",
 		"service": "foo-service@myprovider",
-		"status": "warning"
+		"status": "warning",
+		"using": [
+			"web"
+		]
 	},
 	{
 		"entryPoints": [
@@ -17,7 +20,10 @@
 		"provider": "myprovider",
 		"rule": "Host(`foo.bar`)",
 		"service": "foo-service@myprovider",
-		"status": "disabled"
+		"status": "disabled",
+		"using": [
+			"web"
+		]
 	},
 	{
 		"entryPoints": [
@@ -30,6 +36,9 @@
 		"status": "enabled",
 		"tls": {
 			"passthrough": false
-		}
+		},
+		"using": [
+			"web"
+		]
 	}
 ]
\ No newline at end of file
diff --git a/pkg/api/testdata/tcpservice-bar.json b/pkg/api/testdata/tcpservice-bar.json
index 114f0b74b..ade480a92 100644
--- a/pkg/api/testdata/tcpservice-bar.json
+++ b/pkg/api/testdata/tcpservice-bar.json
@@ -8,6 +8,8 @@
 	},
 	"name": "bar@myprovider",
 	"provider": "myprovider",
+	"status": "enabled",
+	"type": "loadbalancer",
 	"usedBy": [
 		"foo@myprovider",
 		"test@myprovider"
diff --git a/pkg/api/testdata/tcpservices-filtered-search.json b/pkg/api/testdata/tcpservices-filtered-search.json
new file mode 100644
index 000000000..130d5eace
--- /dev/null
+++ b/pkg/api/testdata/tcpservices-filtered-search.json
@@ -0,0 +1,18 @@
+[
+	{
+		"loadBalancer": {
+			"servers": [
+				{
+					"address": "127.0.0.2:2345"
+				}
+			]
+		},
+		"name": "baz@myprovider",
+		"provider": "myprovider",
+		"status": "warning",
+		"type": "loadbalancer",
+		"usedBy": [
+			"foo@myprovider"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/tcpservices-filtered-status.json b/pkg/api/testdata/tcpservices-filtered-status.json
new file mode 100644
index 000000000..03ec085a0
--- /dev/null
+++ b/pkg/api/testdata/tcpservices-filtered-status.json
@@ -0,0 +1,19 @@
+[
+	{
+		"loadBalancer": {
+			"servers": [
+				{
+					"address": "127.0.0.1:2345"
+				}
+			]
+		},
+		"name": "bar@myprovider",
+		"provider": "myprovider",
+		"status": "enabled",
+		"type": "loadbalancer",
+		"usedBy": [
+			"foo@myprovider",
+			"test@myprovider"
+		]
+	}
+]
\ No newline at end of file
diff --git a/pkg/api/testdata/tcpservices-page2.json b/pkg/api/testdata/tcpservices-page2.json
index 345151040..414e0f37d 100644
--- a/pkg/api/testdata/tcpservices-page2.json
+++ b/pkg/api/testdata/tcpservices-page2.json
@@ -9,6 +9,8 @@
 		},
 		"name": "baz@myprovider",
 		"provider": "myprovider",
+		"status": "enabled",
+		"type": "loadbalancer",
 		"usedBy": [
 			"foo@myprovider"
 		]
diff --git a/pkg/api/testdata/tcpservices.json b/pkg/api/testdata/tcpservices.json
index 24320d948..c3f9f7ea6 100644
--- a/pkg/api/testdata/tcpservices.json
+++ b/pkg/api/testdata/tcpservices.json
@@ -10,6 +10,7 @@
 		"name": "bar@myprovider",
 		"provider": "myprovider",
 		"status": "enabled",
+		"type": "loadbalancer",
 		"usedBy": [
 			"foo@myprovider",
 			"test@myprovider"
@@ -26,6 +27,7 @@
 		"name": "baz@myprovider",
 		"provider": "myprovider",
 		"status": "warning",
+		"type": "loadbalancer",
 		"usedBy": [
 			"foo@myprovider"
 		]
@@ -41,6 +43,7 @@
 		"name": "foz@myprovider",
 		"provider": "myprovider",
 		"status": "disabled",
+		"type": "loadbalancer",
 		"usedBy": [
 			"foo@myprovider"
 		]
diff --git a/pkg/config/runtime/runtime_http_test.go b/pkg/config/runtime/runtime_http_test.go
index d95c6e780..909d664b7 100644
--- a/pkg/config/runtime/runtime_http_test.go
+++ b/pkg/config/runtime/runtime_http_test.go
@@ -104,6 +104,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) {
 							Rule:        "Host(`bar.foo`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web"},
 					},
 					"foobar": {
 						Router: &dynamic.Router{
@@ -113,6 +114,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) {
 						},
 						Status: "warning",
 						Err:    []string{`entryPoint "webs" doesn't exist`},
+						Using:  []string{"web"},
 					},
 				},
 			},
@@ -169,6 +171,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) {
 							Rule:        "Host(`bar.foo`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web"},
 					},
 					"foobar": {
 						Router: &dynamic.Router{
@@ -177,6 +180,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) {
 							Rule:        "Host(`bar.foobar`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web", "webs"},
 					},
 				},
 				"webs": {
@@ -188,6 +192,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) {
 							Rule:        "Host(`foo.bar`)",
 						},
 						Status: "enabled",
+						Using:  []string{"webs"},
 					},
 					"foobar": {
 						Router: &dynamic.Router{
@@ -196,6 +201,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) {
 							Rule:        "Host(`bar.foobar`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web", "webs"},
 					},
 				},
 			},
diff --git a/pkg/config/runtime/runtime_tcp_test.go b/pkg/config/runtime/runtime_tcp_test.go
index 9fb3f137e..8df42fc9b 100644
--- a/pkg/config/runtime/runtime_tcp_test.go
+++ b/pkg/config/runtime/runtime_tcp_test.go
@@ -104,6 +104,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) {
 							Rule:        "HostSNI(`bar.foo`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web"},
 					},
 					"foobar": {
 						TCPRouter: &dynamic.TCPRouter{
@@ -111,7 +112,9 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) {
 							Service:     "foobar-service@myprovider",
 							Rule:        "HostSNI(`bar.foobar`)",
 						},
-						Status: "enabled",
+						Status: "warning",
+						Err:    []string{`entryPoint "webs" doesn't exist`},
+						Using:  []string{"web"},
 					},
 				},
 			},
@@ -168,6 +171,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) {
 							Rule:        "HostSNI(`bar.foo`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web"},
 					},
 					"foobar": {
 						TCPRouter: &dynamic.TCPRouter{
@@ -176,6 +180,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) {
 							Rule:        "HostSNI(`bar.foobar`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web", "webs"},
 					},
 				},
 				"webs": {
@@ -187,6 +192,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) {
 							Rule:        "HostSNI(`foo.bar`)",
 						},
 						Status: "enabled",
+						Using:  []string{"webs"},
 					},
 					"foobar": {
 						TCPRouter: &dynamic.TCPRouter{
@@ -195,6 +201,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) {
 							Rule:        "HostSNI(`bar.foobar`)",
 						},
 						Status: "enabled",
+						Using:  []string{"web", "webs"},
 					},
 				},
 			},