diff --git a/api/types/resource.go b/api/types/resource.go
index 9ad63b80526c3..44e77bb5bf910 100644
--- a/api/types/resource.go
+++ b/api/types/resource.go
@@ -588,21 +588,33 @@ func unifiedKindCompare(a, b ResourceWithLabels, isDesc bool) bool {
func unifiedNameCompare(a ResourceWithLabels, b ResourceWithLabels, isDesc bool) bool {
var nameA, nameB string
- resourceA, ok := a.(Server)
- if ok {
- nameA = resourceA.GetHostname()
- } else {
+ switch r := a.(type) {
+ case AppServer:
+ nameA = r.GetApp().GetName()
+ case DatabaseServer:
+ nameA = r.GetDatabase().GetName()
+ case KubeServer:
+ nameA = r.GetCluster().GetName()
+ case Server:
+ nameA = r.GetHostname()
+ default:
nameA = a.GetName()
}
- resourceB, ok := b.(Server)
- if ok {
- nameB = resourceB.GetHostname()
- } else {
- nameB = b.GetName()
+ switch r := b.(type) {
+ case AppServer:
+ nameB = r.GetApp().GetName()
+ case DatabaseServer:
+ nameB = r.GetDatabase().GetName()
+ case KubeServer:
+ nameB = r.GetCluster().GetName()
+ case Server:
+ nameB = r.GetHostname()
+ default:
+ nameB = a.GetName()
}
- return stringCompare(nameA, nameB, isDesc)
+ return stringCompare(strings.ToLower(nameA), strings.ToLower(nameB), isDesc)
}
func (r ResourcesWithLabels) SortByCustom(by SortBy) error {
diff --git a/api/types/resource_test.go b/api/types/resource_test.go
index 2fd99b2ad0dfa..0b6066fe30a77 100644
--- a/api/types/resource_test.go
+++ b/api/types/resource_test.go
@@ -118,6 +118,83 @@ func TestMatchSearch(t *testing.T) {
}
}
+func TestUnifiedNameCompare(t *testing.T) {
+ t.Parallel()
+ testCases := []struct {
+ name string
+ resourceA func(*testing.T) ResourceWithLabels
+ resourceB func(*testing.T) ResourceWithLabels
+ isDesc bool
+ expect bool
+ }{
+ {
+ name: "sort by same kind",
+ resourceA: func(t *testing.T) ResourceWithLabels {
+ server, err := NewServer("node-cloud", KindNode, ServerSpecV2{
+ Hostname: "node-cloud",
+ })
+ require.NoError(t, err)
+ return server
+ },
+ resourceB: func(t *testing.T) ResourceWithLabels {
+ server, err := NewServer("node-strawberry", KindNode, ServerSpecV2{
+ Hostname: "node-strawberry",
+ })
+ require.NoError(t, err)
+ return server
+ },
+ isDesc: true,
+ expect: false,
+ },
+ {
+ name: "sort by different kind",
+ resourceA: func(t *testing.T) ResourceWithLabels {
+ server := newAppServer(t, "app-cloud")
+ return server
+ },
+ resourceB: func(t *testing.T) ResourceWithLabels {
+ server, err := NewServer("node-strawberry", KindNode, ServerSpecV2{
+ Hostname: "node-strawberry",
+ })
+ require.NoError(t, err)
+ return server
+ },
+ isDesc: true,
+ expect: false,
+ },
+ {
+ name: "sort with different cases",
+ resourceA: func(t *testing.T) ResourceWithLabels {
+ server := newAppServer(t, "app-cloud")
+ return server
+ },
+ resourceB: func(t *testing.T) ResourceWithLabels {
+ server, err := NewServer("Node-strawberry", KindNode, ServerSpecV2{
+ Hostname: "node-strawberry",
+ })
+ require.NoError(t, err)
+ return server
+ },
+ isDesc: true,
+ expect: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ tc := tc
+ resourceA := tc.resourceA(t)
+ resourceB := tc.resourceB(t)
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ actual := unifiedNameCompare(resourceA, resourceB, tc.isDesc)
+ if actual != tc.expect {
+ t.Errorf("Expected %v, but got %v for %+v and %+v with isDesc=%v", tc.expect, actual, resourceA, resourceB, tc.isDesc)
+ }
+ })
+ }
+}
+
func TestMatchSearch_ResourceSpecific(t *testing.T) {
t.Parallel()
diff --git a/web/packages/teleport/src/UnifiedResources/FilterPanel.tsx b/web/packages/teleport/src/UnifiedResources/FilterPanel.tsx
index 078df09aa54c9..0eecbc37fa75d 100644
--- a/web/packages/teleport/src/UnifiedResources/FilterPanel.tsx
+++ b/web/packages/teleport/src/UnifiedResources/FilterPanel.tsx
@@ -15,6 +15,7 @@
*/
import React, { useState } from 'react';
+import styled from 'styled-components';
import { ButtonBorder, ButtonPrimary, ButtonSecondary } from 'design/Button';
import { SortDir } from 'design/DataTable/types';
import { Text } from 'design';
@@ -151,6 +152,14 @@ const FilterTypesMenu = ({
setKinds(newKinds);
};
+ const handleSelectAll = () => {
+ setKinds(kindOptions.map(k => k.value));
+ };
+
+ const handleClearAll = () => {
+ setKinds([]);
+ };
+
const applyFilters = () => {
onChange(kinds);
handleClose();
@@ -167,8 +176,9 @@ const FilterTypesMenu = ({
size="small"
onClick={handleOpen}
>
- Type
+ Types {kindsFromParams.length > 0 ? `(${kindsFromParams.length})` : ''}