package api import ( "cmp" "net/url" "sort" "time" ) const ( sortByParam = "sortBy" directionParam = "direction" ) const ( ascendantSorting = "asc" descendantSorting = "desc" sortFieldName = "name" sortFieldStatus = "status" ) type orderedWithName interface { name() string } type orderedRouter interface { orderedWithName provider() string priority() int status() string rule() string service() string entryPointsCount() int } type orderedCertificate interface { orderedWithName status() string issuer() string validUntil() time.Time } func sortRouters[T orderedRouter](values url.Values, routers []T) { sortBy := values.Get(sortByParam) direction := values.Get(directionParam) if direction == "" { direction = ascendantSorting } switch sortBy { case sortFieldName: 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 sortFieldStatus: 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 sortFieldName: 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 sortFieldStatus: 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 sortFieldName: 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 sortFieldStatus: 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 } func sortCertificates[T orderedCertificate](values url.Values, certificates []T) { sortBy := values.Get(sortByParam) direction := values.Get(directionParam) if direction == "" { direction = ascendantSorting } switch sortBy { case sortFieldName, "cn": sortByName(direction, certificates) case sortFieldStatus: sortByFunc(direction, certificates, func(i int) string { return certificates[i].status() }) case "issuer": sortByFunc(direction, certificates, func(i int) string { return certificates[i].issuer() }) case "validUntil": sortByTime(direction, certificates, func(i int) time.Time { return certificates[i].validUntil() }) default: sortByName(direction, certificates) } } func sortByTime[T orderedWithName](direction string, results []T, fn func(int) time.Time) { // Ascending if direction == ascendantSorting { sort.Slice(results, func(i, j int) bool { ti, tj := fn(i), fn(j) if ti.Equal(tj) { return results[i].name() < results[j].name() } return ti.Before(tj) }) return } // Descending sort.Slice(results, func(i, j int) bool { ti, tj := fn(i), fn(j) if ti.Equal(tj) { return results[i].name() > results[j].name() } return ti.After(tj) }) } func sortByName[T orderedWithName](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 cmp.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) }) }