Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v15] Update aws launch buttons with searchable fields #39911

Merged
merged 1 commit into from Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 76 additions & 18 deletions web/packages/shared/components/AwsLaunchButton.tsx
Expand Up @@ -18,7 +18,8 @@

import React from 'react';
import styled from 'styled-components';
import { ButtonBorder, Text } from 'design';
import { space } from 'design/system';
import { ButtonBorder, Flex, Text, Box } from 'design';
import Menu, { MenuItem } from 'design/Menu';
import { ChevronDown } from 'design/Icon';

Expand All @@ -30,14 +31,19 @@ export class AwsLaunchButton extends React.Component<Props> {
state = {
open: false,
anchorEl: null,
filtered: '',
};

onOpen = () => {
this.setState({ open: true });
this.setState({ open: true, filtered: '' });
};

onClose = () => {
this.setState({ open: false });
this.setState({ open: false, filtered: '' });
};

onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ filtered: event.target.value });
};

render() {
Expand All @@ -57,8 +63,9 @@ export class AwsLaunchButton extends React.Component<Props> {
</ButtonBorder>
<Menu
menuListCss={() => ({
overflow: 'auto',
overflow: 'hidden',
minWidth: '180px',
maxHeight: '400px',
})}
transformOrigin={{
vertical: 'top',
Expand All @@ -74,10 +81,19 @@ export class AwsLaunchButton extends React.Component<Props> {
onClose={this.onClose}
>
<RoleItemList
awsRoles={awsRoles}
awsRoles={awsRoles.filter(role => {
const lowerFilter = this.state.filtered.toLowerCase();
const lowerDisplay = role.display.toLowerCase();
const lowerName = role.name.toLowerCase();
return (
lowerDisplay.includes(lowerFilter) ||
lowerName.includes(lowerFilter)
);
})}
getLaunchUrl={getLaunchUrl}
onLaunchUrl={onLaunchUrl}
closeMenu={this.onClose}
onChange={this.onChange}
/>
</Menu>
</>
Expand All @@ -89,10 +105,14 @@ function RoleItemList({
awsRoles,
getLaunchUrl,
closeMenu,
onChange,
onLaunchUrl,
}: Props & { closeMenu: () => void }) {
}: Props & {
closeMenu: () => void;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}) {
const awsRoleItems = awsRoles.map((item, key) => {
const { display, arn } = item;
const { display, arn, name } = item;
const launchUrl = getLaunchUrl(arn);
return (
<StyledMenuItem
Expand All @@ -108,32 +128,47 @@ function RoleItemList({
onLaunchUrl?.(item.arn);
}}
>
<Text style={{ maxWidth: '25ch' }}>{display}</Text>
<Text>{`${display !== name ? `${display} (${name})` : display}`}</Text>
</StyledMenuItem>
);
});

return (
<>
<Flex flexDirection="column">
<Text
px="2"
fontSize="11px"
mb="2"
css={`
color: ${props => props.theme.colors.text.main};
background: ${props => props.theme.colors.spotBackground[2]};
`}
>
Select IAM Role
</Text>
{awsRoleItems.length ? (
awsRoleItems
) : (
<Text px={2} m={2} color="text.disabled">
No roles found
</Text>
)}
</>
<StyledInput
p="2"
m="2"
type="text"
onChange={onChange}
autoFocus
placeholder={'Search IAM roles...'}
autoComplete="off"
/>
<Box
css={`
max-height: 220px;
overflow: auto;
`}
>
{awsRoleItems.length ? (
awsRoleItems
) : (
<Text px={2} m={2} color="text.disabled">
No roles found
</Text>
)}
</Box>
</Flex>
);
}

Expand All @@ -159,3 +194,26 @@ const StyledMenuItem = styled(MenuItem)(
}
`
);

const StyledInput = styled.input(
({ theme }) => `
background: transparent;
border: 1px solid ${theme.colors.text.muted};
border-radius: 4px;
box-sizing: border-box;
color: ${theme.colors.text.main};
height: 32px;
outline: none;

&:focus, &:hover {
border 1px solid ${theme.colors.text.slightlyMuted};
outline: none;
}

::placeholder {
color: ${theme.colors.text.muted};
opacity: 1;
}
`,
space
);
Expand Up @@ -32,6 +32,7 @@ import { desktops } from 'teleport/Desktops/fixtures';
import { nodes } from 'teleport/Nodes/fixtures';

import makeApp from 'teleport/services/apps/makeApps';
import { ResourceActionButton } from 'teleport/UnifiedResources/ResourceActionButton';

import {
makeUnifiedResourceViewItemApp,
Expand Down Expand Up @@ -104,7 +105,9 @@ export const Cards: Story = {
<Grid gap={2}>
{[
...apps.map(resource =>
makeUnifiedResourceViewItemApp(resource, { ActionButton })
makeUnifiedResourceViewItemApp(resource, {
ActionButton: <ResourceActionButton resource={resource} />,
})
),
...databases.map(resource =>
makeUnifiedResourceViewItemDatabase(resource, {
Expand All @@ -115,7 +118,9 @@ export const Cards: Story = {
makeUnifiedResourceViewItemKube(resource, { ActionButton })
),
...nodes.map(resource =>
makeUnifiedResourceViewItemNode(resource, { ActionButton })
makeUnifiedResourceViewItemNode(resource, {
ActionButton,
})
),
...additionalResources.map(resource =>
makeUnifiedResourceViewItemApp(resource, { ActionButton })
Expand Down
1 change: 1 addition & 0 deletions web/packages/shared/services/apps.ts
Expand Up @@ -17,6 +17,7 @@
*/

export type AwsRole = {
name: string;
arn: string;
display: string;
};
14 changes: 14 additions & 0 deletions web/packages/teleport/src/Apps/fixtures/index.ts
Expand Up @@ -151,13 +151,27 @@ export const apps = [
awsConsole: true,
awsRoles: [
{
name: 'role name',
arn: 'arn:aws:iam::joe123:role/EC2FullAccess',
display: 'EC2FullAccess',
},
{
name: 'other role name',
arn: 'arn:aws:iam::joe123:role/EC2FullAccess',
display: 'ReallyLonReallyLonggggggEC2FullAccess',
},
{
name: 'thisthing',
arn: 'arn:aws:iam::joe123:role/EC2ReadOnly',
display: 'EC2ReadOnly',
},
...new Array(20).fill(undefined).map((_, index) => {
return {
name: `long-${index}`,
arc: `arn:aws:iam::${index}`,
display: `LONG${index}`,
};
}),
],
clusterId: 'one',
fqdn: 'awsconsole-1.com',
Expand Down