Skip to content

Commit

Permalink
Add a search panel to the unified resources list (gravitational#29878)
Browse files Browse the repository at this point in the history
* Update resourceKind method to return slice

* Add UnifiedResourceComponent

* Add unifiedResourceWatcher to auth server

* Init unified resource watcher

* Convert to strings for error output

* Add license

* Add some tests

* Fix a race condition and properly close the watcher

* add defer

* Add kubes to watcher

* Add uninit check

* Notify if stale

* Add SAML to watcher

* Update test and fix lint

* Fix license

* Add godocs

* Add Kind to ui structs

* Add FakePaginateParams

* Update ListUnifiedResources proto messages

* Update uses of FakePaginate

* Include type in seenmap for MatchResourceByFilters

* Update TestAuthServer with unified resource watcher

* CheckAndSetDefaults for ListUnifiedResourcesRequest

* Add ListUnifiedResources rpc

* Update protos

* Add test

* Add docs and update tests

* Add more docs

* Combine app types in switch

* Fix tests

* Add kube access checker

* Add remaining kinds

* Add a Unified Resource card component

* Lint fixes

* WIP: Unified Resource list

* Review feedback

* Add configurable queuesize to NewWatcher

* Move Get to collector

* Review comments

* Rename AgentKind and fix lint issues

* make unified resource cache public

* Move to common interface

* Use copy as value

* Move saml getter

* Review comments

* WIP: infinite scrolling

* Prevent VS Code from showing errors in tsconfig

If tsconfig doesn't have an `outDir` option set, VS Code shows multitude of errors of the following kind:

```
Cannot write file '/Users/bartosz/code/teleport/e/web/teleport/babel.config.js' because it would overwrite input file.
```

* Remove unnecessary files

* todo

* Cleanup

* Add license

* invert watcher and cache

* Fix imports

* Fix lint: check for err

* Add Kind to ui structs

* Add FakePaginateParams

* Update ListUnifiedResources proto messages

* Update uses of FakePaginate

* Include type in seenmap for MatchResourceByFilters

* Update TestAuthServer with unified resource watcher

* CheckAndSetDefaults for ListUnifiedResourcesRequest

* Add ListUnifiedResources rpc

* Update protos

* Add test

* Add docs and update tests

* Add more docs

* Combine app types in switch

* Fix tests

* Add kube access checker

* Add remaining kinds

* use cache instead of watcher

* review round 1

* Update test

* Review, fixing broken tests

* Add copy to get

* Add a search panel to the unified resources list

* Add CloneAny interface to help with unified resource cloning

* Use cloneany

* Add sort for name and kind

* Add app and saml, fix tests

* Review

* Review

* Lint

* Watch kube server instead of cluster

* Watch kube server instead of cluster

* Get kubecluster from server

* update proto file

* Add no access test case

* Review, fix CI problems

* One more lint fix

* Yet another lint fix

* move sorting

* Fix sorting by name

* Review feedback round 2

* Remove unneeded bk return

* Keep grpc naming convention

* Add missing docs

* defer log

* Fix the useEffect dependency list

* Review feedback

* Adds a UnifiedResource watcher to the auth server. This watcher
will watch all the types that are displayed in the web UI and store
them in-memory to allow us to search/filter/query and get multiple
kinds returned at the same time.
[RFD](gravitational#28162)

* Add ListUnifiedResources gRPC and web endpoints (gravitational#29661)

Adds the `ListUnifiedResources` grpc endpoint that returns paginated unified resources and exposes it in the web apiserver

---------

Co-authored-by: Michael Myers <[email protected]>
  • Loading branch information
bl-nero and avatus authored Aug 16, 2023
1 parent da325ed commit eee42ce
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 24 deletions.
14 changes: 13 additions & 1 deletion web/packages/teleport/src/UnifiedResources/Resources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ import useTeleport from 'teleport/useTeleport';

import { useResources } from './useResources';
import { ResourceCard } from './ResourceCard';
import SearchPanel from './SearchPanel';

export function Resources() {
const teleCtx = useTeleport();
const { attempt, fetchedData, fetchMore } = useResources(teleCtx);
const {
attempt,
fetchedData,
fetchMore,
filtering: { pathname, params, setParams, replaceHistory },
} = useResources(teleCtx);
const observed = React.useRef(null);

React.useEffect(() => {
Expand All @@ -52,6 +58,12 @@ export function Resources() {
<FeatureHeader alignItems="center" justifyContent="space-between">
<FeatureHeaderTitle>Resources</FeatureHeaderTitle>
</FeatureHeader>
<SearchPanel
params={params}
setParams={setParams}
pathname={pathname}
replaceHistory={replaceHistory}
/>
{attempt.status === 'failed' && (
<ErrorMessage message={attempt.statusText} />
)}
Expand Down
101 changes: 101 additions & 0 deletions web/packages/teleport/src/UnifiedResources/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Copyright 2023 Gravitational, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React, { SetStateAction } from 'react';
import styled from 'styled-components';

import { height, space, color } from 'design/system';

// Taken from design.dataTable.InputSearch; will be modified later.
export function SearchInput({ searchValue, setSearchValue, children }: Props) {
return (
<WrapperBackground>
<Wrapper>
<StyledInput
placeholder="Search for resources..."
px={3}
value={searchValue}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setSearchValue(e.target.value)
}
/>
<ChildWrapperBackground>
<ChildWrapper>{children}</ChildWrapper>
</ChildWrapperBackground>
</Wrapper>
</WrapperBackground>
);
}

type Props = {
searchValue: string;
setSearchValue: React.Dispatch<SetStateAction<string>>;
children?: JSX.Element;
};

const ChildWrapper = styled.div`
position: relative;
height: 100%;
right: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 200px 200px 0;
`;

const ChildWrapperBackground = styled.div`
position: absolute;
height: 100%;
right: 0;
display: flex;
align-items: center;
justify-content: center;
border-left: ${props => props.theme.borders[1]}
${props => props.theme.colors.spotBackground[0]};
border-radius: 0 200px 200px 0;
`;

const Wrapper = styled.div`
position: relative;
display: flex;
overflow: hidden;
width: 100%;
border-radius: 200px;
height: 32px;
background: transparent;
`;

const WrapperBackground = styled.div`
background: ${props => props.theme.colors.levels.sunken};
border-radius: 200px;
width: 100%;
height: 32px;
`;

const StyledInput = styled.input`
border: none;
outline: none;
box-sizing: border-box;
height: 100%;
font-size: 12px;
width: 100%;
transition: all 0.2s;
${color}
${space}
${height}
background: ${props => props.theme.colors.spotBackground[0]};
padding-right: 184px;
`;
70 changes: 70 additions & 0 deletions web/packages/teleport/src/UnifiedResources/SearchPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright 2023 Gravitational, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import styled from 'styled-components';
import { Text, Flex } from 'design';
import { PredicateDoc } from 'shared/components/Search/PredicateDoc';

import Toggle from 'teleport/components/Toggle';

import useServersideSearchPanel, {
SearchPanelState,
HookProps,
} from 'teleport/components/ServersideSearchPanel/useServerSideSearchPanel';
import Tooltip from 'teleport/components/ServersideSearchPanel/Tooltip';

import { SearchInput } from './SearchInput';

export default function Container(props: HookProps) {
const state = useServersideSearchPanel(props);
return <SearchPanel {...state} />;
}

// Adapted from teleport.components.ServersideSearchPanel
export function SearchPanel({
searchString,
setSearchString,
isAdvancedSearch,
setIsAdvancedSearch,
onSubmitSearch,
}: SearchPanelState) {
function onToggle() {
setIsAdvancedSearch(wasAdvancedSearch => !wasAdvancedSearch);
}

return (
<Flex as="form" style={{ width: '70%' }} onSubmit={onSubmitSearch} mb={2}>
<SearchInput searchValue={searchString} setSearchValue={setSearchString}>
<ToggleWrapper>
<Toggle isToggled={isAdvancedSearch} onToggle={onToggle} />
<Text typography="paragraph2">Advanced</Text>
<Tooltip>
<PredicateDoc />
</Tooltip>
</ToggleWrapper>
</SearchInput>
</Flex>
);
}

const ToggleWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-around;
padding-inline: ${props => props.theme.space[3]}px;
width: 120px;
`;
15 changes: 6 additions & 9 deletions web/packages/teleport/src/UnifiedResources/useResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,14 @@ import Ctx from 'teleport/teleportContext';
import useStickyClusterId from 'teleport/useStickyClusterId';
import { useUrlFiltering } from 'teleport/components/hooks';
import { useInfiniteScroll } from 'teleport/components/hooks/useInfiniteScroll';
import {
AgentFilter,
AgentResponse,
UnifiedResource,
} from 'teleport/services/agents';
import { AgentResponse, UnifiedResource } from 'teleport/services/agents';
import { UrlFilteringState } from 'teleport/components/hooks/useUrlFiltering/useUrlFiltering';

export interface ResourcesState {
fetchedData: AgentResponse<UnifiedResource>;
params: AgentFilter;
fetchMore: () => void;
attempt: Attempt;
filtering: UrlFilteringState;
}

/**
Expand All @@ -43,10 +40,11 @@ export interface ResourcesState {
export function useResources(ctx: Ctx): ResourcesState {
const { clusterId } = useStickyClusterId();

const { params, search, ...filteringProps } = useUrlFiltering({
const filtering = useUrlFiltering({
fieldName: 'name',
dir: 'ASC',
});
const { params, search } = filtering;

const { fetchInitial, fetchedData, attempt, fetchMore } = useInfiniteScroll({
fetchFunc: ctx.resourceService.fetchUnifiedResources,
Expand All @@ -59,10 +57,9 @@ export function useResources(ctx: Ctx): ResourcesState {
}, [clusterId, search]);

return {
...filteringProps,
fetchedData,
params,
fetchMore,
attempt,
filtering,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,35 @@ import { PredicateDoc } from 'shared/components/Search/PredicateDoc';

import Toggle from 'teleport/components/Toggle';

import { PageIndicators } from 'teleport/components/hooks/useServersidePagination';

import Tooltip from './Tooltip';
import useServersideSearchPanel, {
State,
Props,
SearchPanelState,
HookProps,
} from './useServerSideSearchPanel';

interface ComponentProps {
pageIndicators: PageIndicators;
disabled?: boolean;
}

export interface Props extends HookProps, ComponentProps {}

export default function Container(props: Props) {
const state = useServersideSearchPanel(props);
return <ServersideSearchPanel {...state} />;
const { pageIndicators, disabled, ...hookProps } = props;
const state = useServersideSearchPanel(hookProps);
return (
<ServersideSearchPanel
{...state}
pageIndicators={pageIndicators}
disabled={disabled}
/>
);
}

interface State extends SearchPanelState, ComponentProps {}

export function ServersideSearchPanel({
searchString,
setSearchString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import { AgentFilter } from 'teleport/services/agents';

import { encodeUrlQueryParams } from 'teleport/components/hooks/useUrlFiltering';

import type { PageIndicators } from '../hooks/useServersidePagination';

export default function useServersideSearchPanel(props: Props) {
const { pathname, params, setParams, replaceHistory } = props;

export default function useServersideSearchPanel({
pathname,
params,
setParams,
replaceHistory,
}: HookProps) {
const [searchString, setSearchString] = useState('');
const [isAdvancedSearch, setIsAdvancedSearch] = useState(false);
const [isInitialLoad, setIsInitialLoad] = useState(true);
Expand Down Expand Up @@ -82,7 +83,6 @@ export default function useServersideSearchPanel(props: Props) {
isAdvancedSearch,
setIsAdvancedSearch,
onSubmitSearch,
...props,
};
}

Expand All @@ -95,13 +95,11 @@ function decodeUrlQueryParam(param: string) {
return decodedQuery;
}

export type Props = {
export type HookProps = {
pathname: string;
replaceHistory: (path: string) => void;
params: AgentFilter;
setParams: (params: AgentFilter) => void;
pageIndicators: PageIndicators;
disabled?: boolean;
};

export type State = ReturnType<typeof useServersideSearchPanel>;
export type SearchPanelState = ReturnType<typeof useServersideSearchPanel>;

0 comments on commit eee42ce

Please sign in to comment.