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})` : ''} + {kindsFromParams.length > 0 && } `margin-top: 36px;`} @@ -184,6 +194,30 @@ const FilterTypesMenu = ({ open={Boolean(anchorEl)} onClose={cancelUpdate} > + + + Select All + + + Clear All + + {kindOptions.map(kind => ( props.theme.colors.brand}; + border-radius: 50%; + display: inline-block; +`; diff --git a/web/packages/teleport/src/UnifiedResources/Resources.tsx b/web/packages/teleport/src/UnifiedResources/Resources.tsx index 9befd75985680..9f213115ef737 100644 --- a/web/packages/teleport/src/UnifiedResources/Resources.tsx +++ b/web/packages/teleport/src/UnifiedResources/Resources.tsx @@ -17,14 +17,7 @@ limitations under the License. import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { - Box, - Indicator, - Flex, - ButtonLink, - ButtonSecondary, - Text, -} from 'design'; +import { Box, Flex, ButtonLink, ButtonSecondary, Text } from 'design'; import { Magnifier } from 'design/Icon'; import { Danger } from 'design/Alert'; @@ -171,9 +164,6 @@ export function Resources() {
- - - {attempt.status === 'failed' && resources.length > 0 && ( Load more )} @@ -253,13 +243,6 @@ const ListFooter = styled.div` text-align: center; `; -// Line height is set to 0 to prevent the layout engine from adding extra pixels -// to the element's height. -const IndicatorContainer = styled(Box)` - display: ${props => (props.status === 'processing' ? 'block' : 'none')}; - line-height: 0; -`; - const emptyStateInfo: EmptyStateInfo = { title: 'Add your first resource to Teleport', byline: diff --git a/web/packages/teleport/src/services/apps/makeApps.ts b/web/packages/teleport/src/services/apps/makeApps.ts index 8b9def82bd257..19b05ce5c88c4 100644 --- a/web/packages/teleport/src/services/apps/makeApps.ts +++ b/web/packages/teleport/src/services/apps/makeApps.ts @@ -84,7 +84,7 @@ export default function makeApp(json: any): App { } function guessAppIcon(json: any): GuessedAppType { - const { name, labels, awsConsole = false } = json; + const { name, labels, friendlyName, awsConsole = false } = json; if (awsConsole) { return 'Aws'; @@ -92,6 +92,7 @@ function guessAppIcon(json: any): GuessedAppType { if ( name?.toLocaleLowerCase().includes('slack') || + friendlyName?.toLocaleLowerCase().includes('slack') || labels?.some(l => `${l.name}:${l.value}` === 'icon:slack') ) { return 'Slack'; @@ -99,6 +100,7 @@ function guessAppIcon(json: any): GuessedAppType { if ( name?.toLocaleLowerCase().includes('grafana') || + friendlyName?.toLocaleLowerCase().includes('grafana') || labels?.some(l => `${l.name}:${l.value}` === 'icon:grafana') ) { return 'Grafana'; @@ -106,6 +108,7 @@ function guessAppIcon(json: any): GuessedAppType { if ( name?.toLocaleLowerCase().includes('jenkins') || + friendlyName?.toLocaleLowerCase().includes('jenkins') || labels?.some(l => `${l.name}:${l.value}` === 'icon:jenkins') ) { return 'Jenkins';