From 8d4610a5951196eb719b4ea2627b8fbf7c816ac8 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 21 Nov 2024 12:10:46 +0100 Subject: [PATCH 1/4] Remove resource lists --- .../NewRequest/ResourceList/Apps.tsx | 294 ------------------ .../NewRequest/ResourceList/Databases.tsx | 76 ----- .../NewRequest/ResourceList/Desktops.tsx | 70 ----- .../NewRequest/ResourceList/Kubes.tsx | 66 ---- .../NewRequest/ResourceList/Nodes.tsx | 86 ----- .../ResourceList/ResourceList.story.tsx | 231 -------------- .../NewRequest/ResourceList/ResourceList.tsx | 141 --------- .../NewRequest/ResourceList/UserGroups.tsx | 77 ----- 8 files changed, 1041 deletions(-) delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Apps.tsx delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Databases.tsx delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Desktops.tsx delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Kubes.tsx delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Nodes.tsx delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.story.tsx delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.tsx delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/UserGroups.tsx diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Apps.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Apps.tsx deleted file mode 100644 index 5488ceeedaefd..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Apps.tsx +++ /dev/null @@ -1,294 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React, { useState, useEffect } from 'react'; -import styled from 'styled-components'; -import { components } from 'react-select'; -import { Flex, Text, ButtonBorder, ButtonPrimary } from 'design'; -import { ClickableLabelCell, Cell } from 'design/DataTable'; - -import { App } from 'teleport/services/apps'; - -import Select, { - Option as BaseOption, - CustomSelectComponentProps, -} from 'shared/components/Select'; -import { ToolTipInfo } from 'shared/components/ToolTip'; - -import { ResourceMap, RequestableResourceKind } from '../resource'; - -import { ListProps, StyledTable } from './ResourceList'; - -type Option = BaseOption & { - isSelected?: boolean; -}; - -export function Apps(props: ListProps & { apps: App[] }) { - const { - apps = [], - addedResources, - customSort, - onLabelClick, - addOrRemoveResource, - } = props; - return ( - ( - - ), - }, - { - altKey: 'action-btn', - render: agent => ( - - ), - }, - ]} - emptyText="No Results Found" - customSort={customSort} - disableFilter - /> - ); -} - -const OptionComponent = ( - props: CustomSelectComponentProps< - { toggleUserGroup(groupId: string, groupDescription: string): void }, - Option - > -) => { - const { toggleUserGroup } = props.selectProps.customProps; - return ( - - toggleUserGroup(props.value, props.label)} - py="8px" - px="12px" - > - {' '} - {props.label} - - - ); -}; - -function ActionCell({ - agent, - addedResources, - addOrRemoveResource, -}: { - agent: App; - addedResources: ResourceMap; - addOrRemoveResource: ( - kind: RequestableResourceKind, - resourceId: string, - resourceName?: string - ) => void; -}) { - const [userGroupOptions] = useState(() => { - return agent.userGroups.map(ug => { - return { label: ug.description, value: ug.name }; - }); - }); - const [selectedGroups, setSelectedGroups] = useState([]); - - useEffect(() => { - if (userGroupOptions.length === 0) { - return; - } - - // Applications can refer to the same user group id. - // When user selects an option from one row, we need - // to update selected groups for all other rows. - const updatedSelectedGroups = userGroupOptions.flatMap(o => { - if (addedResources.user_group[o.value]) { - return { ...o, isSelected: true }; - } - return []; // skip this option - }); - setSelectedGroups(updatedSelectedGroups); - - // A user can only select an app OR user groups. - // If a user selected a user group from one row, - // that is also applicable to this row, - // remove app from selection. - if (addedResources.app[agent.name] && updatedSelectedGroups.length > 0) { - addOrRemoveResource('app', agent.name); - } - }, [addedResources]); - - function handleSelectedGroups(o: Option[]) { - // Deselect the app if a user is selecting from a list of groups - // for the first time. - if (selectedGroups.length === 0 && addedResources.app[agent.name]) { - addOrRemoveResource('app', agent.name); // remove app from selection. - } - - setSelectedGroups(o); - } - - function toggleUserGroup(id: string, description = '') { - addOrRemoveResource('user_group', id, description); - } - - function toggleApp() { - addOrRemoveResource('app', agent.name, agent.friendlyName); - } - - const isAppAdded = Boolean(addedResources.app[agent.name]); - const hasSelectedGroups = selectedGroups.length > 0; - - if (!isAppAdded && !hasSelectedGroups) { - return ( - - - + Add to request - - - ); - } - - if (isAppAdded && agent.userGroups.length === 0) { - return ( - - - Remove - - - ); - } - - // Remove button is only shown when user has not added user - // groups yet, but has the option to do so - const showRemoveButton = isAppAdded && !hasSelectedGroups; - - return ( - - {showRemoveButton && ( - - Remove - - )} - - - This application {agent.name} can be alternatively requested by - members of user groups. You can alternatively select user groups - instead to access this application. - - - - - ); -} - -const StyledSelect = styled(Select)` - margin-left: 8px; - - input[type='checkbox'] { - cursor: pointer; - } - - .react-select__control { - width: 260px; - } - - .react-select__option { - padding: 0; - } - - .react-select__value-container { - position: static; - } - - &.hasSelectedGroups { - .react-select__control { - background: ${p => p.theme.colors.interactive.solid.primary.default}; - border: transparent; - } - - .react-select__placeholder, - .react-select__dropdown-indicator { - color: ${p => p.theme.colors.text.primaryInverse}; - } - } -`; diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Databases.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Databases.tsx deleted file mode 100644 index 3f6f1e69da38c..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Databases.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; -import { ClickableLabelCell } from 'design/DataTable'; -import { Database } from 'teleport/services/databases'; - -import { ListProps, StyledTable, renderActionCell } from './ResourceList'; - -export function Databases(props: ListProps & { databases: Database[] }) { - const { - databases = [], - onLabelClick, - addedResources, - addOrRemoveResource, - requestStarted, - customSort, - } = props; - - return ( - ( - - ), - }, - { - altKey: 'action-btn', - render: agent => - renderActionCell( - Boolean(addedResources.db[agent.name]), - requestStarted, - () => addOrRemoveResource('db', agent.name) - ), - }, - ]} - emptyText="No Results Found" - customSort={customSort} - disableFilter - /> - ); -} diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Desktops.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Desktops.tsx deleted file mode 100644 index 7dcde05b2b33d..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Desktops.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; -import { ClickableLabelCell } from 'design/DataTable'; -import { Desktop } from 'teleport/services/desktops'; - -import { ListProps, StyledTable, renderActionCell } from './ResourceList'; - -export function Desktops(props: ListProps & { desktops: Desktop[] }) { - const { - desktops = [], - addedResources, - customSort, - onLabelClick, - requestStarted, - addOrRemoveResource, - } = props; - - return ( - ( - - ), - }, - { - altKey: 'action-btn', - render: agent => - renderActionCell( - Boolean(addedResources.windows_desktop[agent.name]), - requestStarted, - () => addOrRemoveResource('windows_desktop', agent.name) - ), - }, - ]} - emptyText="No Results Found" - customSort={customSort} - disableFilter - /> - ); -} diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Kubes.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Kubes.tsx deleted file mode 100644 index b6e79ed45329a..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Kubes.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; -import { ClickableLabelCell } from 'design/DataTable'; -import { Kube } from 'teleport/services/kube'; - -import { ListProps, StyledTable, renderActionCell } from './ResourceList'; - -export function Kubes(props: ListProps & { kubes: Kube[] }) { - const { - kubes = [], - addedResources, - requestStarted, - customSort, - onLabelClick, - addOrRemoveResource, - } = props; - - return ( - ( - - ), - }, - { - altKey: 'action-btn', - render: agent => - renderActionCell( - Boolean(addedResources.kube_cluster[agent.name]), - requestStarted, - () => addOrRemoveResource('kube_cluster', agent.name) - ), - }, - ]} - emptyText="No Results Found" - customSort={customSort} - disableFilter - /> - ); -} diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Nodes.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Nodes.tsx deleted file mode 100644 index dfcb2021445ab..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Nodes.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; -import { Cell, ClickableLabelCell } from 'design/DataTable'; -import { Node } from 'teleport/services/nodes'; - -import { ListProps, StyledTable, renderActionCell } from './ResourceList'; - -export function Nodes(props: ListProps & { nodes: Node[] }) { - const { - nodes = [], - addedResources, - customSort, - onLabelClick, - requestStarted, - addOrRemoveResource, - } = props; - - return ( - ( - - ), - }, - { - altKey: 'action-btn', - render: agent => - renderActionCell( - Boolean(addedResources.node[agent.id]), - requestStarted, - () => addOrRemoveResource('node', agent.id, agent.hostname) - ), - }, - ]} - emptyText="No Results Found" - customSort={customSort} - disableFilter - /> - ); -} - -export const renderAddressCell = ({ addr, tunnel }: Node) => ( - {tunnel ? renderTunnel() : addr} -); - -function renderTunnel() { - return ( - - ← tunnel - - ); -} diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.story.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.story.tsx deleted file mode 100644 index 98c538c03ca27..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.story.tsx +++ /dev/null @@ -1,231 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; - -import { Desktop } from 'teleport/services/desktops'; -import { Database } from 'teleport/services/databases'; -import { App } from 'teleport/services/apps'; -import { Kube } from 'teleport/services/kube'; -import { Node } from 'teleport/services/nodes'; -import { UserGroup } from 'teleport/services/userGroups'; - -import { getEmptyResourceState } from '../resource'; - -import { ResourceList, ResourceListProps } from './ResourceList'; - -export default { - title: 'Shared/AccessRequests/ResourceList', -}; - -export const Apps = () => ( - -); - -export const Databases = () => ( - -); - -export const Desktops = () => ( - -); - -export const Kubes = () => ( - -); - -export const Nodes = () => ( - -); - -export const Roles = () => ( - -); - -export const UserGroups = () => ( - -); - -export const SamlApps = () => ; - -const props: ResourceListProps = { - agents: [], - selectedResource: 'app', - customSort: { dir: 'ASC', fieldName: '', onSort: () => null }, - requestStarted: false, - onLabelClick: () => null, - addedResources: getEmptyResourceState(), - addOrRemoveResource: () => null, - requestableRoles: [], - disableRows: false, -}; - -const apps: App[] = [ - { - name: 'aws-console-1', - kind: 'app', - uri: 'https://console.aws.amazon.com/ec2/v2/home', - publicAddr: 'awsconsole-1.teleport-proxy.com', - addrWithProtocol: 'https://awsconsole-1.teleport-proxy.com', - labels: [ - { - name: 'aws_account_id', - value: 'A1234', - }, - ], - description: 'This is an AWS Console app', - awsConsole: true, - samlApp: false, - awsRoles: [], - clusterId: 'one', - fqdn: 'awsconsole-1.com', - id: 'one-aws-console-1-awsconsole-1.teleport-proxy.com', - launchUrl: '', - userGroups: [], - }, - { - name: 'aws-console-2', - kind: 'app', - uri: 'https://console.aws.amazon.com/ec2/v2/home', - publicAddr: 'awsconsole-2.teleport-proxy.com', - addrWithProtocol: 'https://awsconsole-2.teleport-proxy.com', - labels: [ - { - name: 'aws_account_id', - value: 'A1235', - }, - ], - description: 'This is another AWS Console app', - awsConsole: true, - samlApp: false, - awsRoles: [], - clusterId: 'one', - fqdn: 'awsconsole-2.com', - id: 'one-aws-console-2-awsconsole-2.teleport-proxy.com', - launchUrl: '', - userGroups: [ - { name: 'admins', description: 'Admins' }, - { name: 'users', description: 'Regular users' }, - ], - }, -]; - -const nodes: Node[] = [ - { - tunnel: false, - kind: 'node', - subKind: 'teleport', - sshLogins: ['dev', 'root'], - id: '104', - clusterId: 'one', - hostname: 'fujedu', - addr: '172.10.1.20:3022', - labels: [ - { - name: 'cluster', - value: 'one', - }, - ], - }, -]; - -const dbs: Database[] = [ - { - name: 'aurora', - kind: 'db', - description: 'PostgreSQL 11.6: AWS Aurora ', - hostname: 'aurora-hostname', - type: 'RDS PostgreSQL', - protocol: 'postgres', - labels: [{ name: 'cluster', value: 'root' }], - }, -]; - -const desktops: Desktop[] = [ - { - os: 'windows', - kind: 'windows_desktop', - name: 'bb8411a4-ba50-537c-89b3-226a00447bc6', - addr: 'host.com', - labels: [{ name: 'foo', value: 'bar' }], - logins: ['Administrator'], - }, -]; - -const kubes: Kube[] = [ - { - name: 'tele.logicoma.dev-prod', - kind: 'kube_cluster', - labels: [{ name: 'env', value: 'prod' }], - }, -]; - -const userGroups: UserGroup[] = [ - { - kind: 'user_group', - name: 'group id 1', - description: 'user group', - labels: [{ name: 'env', value: 'prod' }], - }, - { - kind: 'user_group', - name: 'group id 2', - description: 'admin group', - labels: [{ name: 'env', value: 'dev' }], - }, -]; - -const samlApp: App[] = [ - { - name: 'app_saml', - kind: 'app', - uri: 'https://example.com/saml', - publicAddr: 'example.com', - addrWithProtocol: 'https://example.com/saml', - labels: [ - { - name: 'env', - value: 'dev', - }, - ], - description: 'This is a SAML app', - awsConsole: false, - samlApp: true, - awsRoles: [], - clusterId: 'one', - fqdn: 'example.com', - id: 'example.com/saml', - launchUrl: '', - userGroups: [], - }, -]; diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.tsx deleted file mode 100644 index 2981f1175ebd3..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/ResourceList.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; -import styled from 'styled-components'; -import { ButtonBorder, ButtonPrimary, Box } from 'design'; -import Table, { Cell } from 'design/DataTable'; -import { Desktop } from 'teleport/services/desktops'; -import { Database } from 'teleport/services/databases'; -import { App } from 'teleport/services/apps'; -import { Kube } from 'teleport/services/kube'; -import { Node } from 'teleport/services/nodes'; -import { UserGroup } from 'teleport/services/userGroups'; -import { CustomSort } from 'design/DataTable/types'; - -import { ResourceLabel, UnifiedResource } from 'teleport/services/agents'; - -import { ResourceMap, RequestableResourceKind } from '../resource'; - -import { Apps } from './Apps'; -import { Databases } from './Databases'; -import { Nodes } from './Nodes'; -import { Desktops } from './Desktops'; -import { Kubes } from './Kubes'; -import { Roles } from './Roles'; -import { UserGroups } from './UserGroups'; - -export function ResourceList(props: ResourceListProps) { - const { - agents, - disableRows, - selectedResource, - requestableRoles, - ...listProps - } = props; - - return ( - - {selectedResource === 'app' && ( - - )} - {selectedResource === 'db' && ( - - )} - {selectedResource === 'node' && ( - - )} - {selectedResource === 'windows_desktop' && ( - - )} - {selectedResource === 'kube_cluster' && ( - - )} - {selectedResource === 'role' && ( - - )} - {selectedResource === 'user_group' && ( - - )} - - ); -} - -export const StyledTable = styled(Table)` - & > tbody > tr > td { - vertical-align: middle; - } -` as typeof Table; - -const Wrapper = styled(Box)` - &.disabled { - pointer-events: none; - opacity: 0.5; - } -`; - -export function renderActionCell( - isAgentAdded: boolean, - requestStarted: boolean, - toggleAgent: () => void -) { - const text = requestStarted ? '+ Add to request' : '+ Request Access'; - return ( - - {isAgentAdded ? ( - - Remove - - ) : ( - - {text} - - )} - - ); -} - -export type ListProps = { - customSort: CustomSort; - requestStarted: boolean; - onLabelClick: (label: ResourceLabel) => void; - addedResources: ResourceMap; - addOrRemoveResource: ( - kind: RequestableResourceKind, - resourceId: string, - resourceName?: string - ) => void; - requestableRoles?: string[]; -}; - -export type ResourceListProps = { - agents: UnifiedResource[]; - selectedResource: RequestableResourceKind; - // disableRows disable clicking on any buttons (when fetching). - disableRows: boolean; -} & ListProps; diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/UserGroups.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/UserGroups.tsx deleted file mode 100644 index e96e396127543..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/UserGroups.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; -import { ClickableLabelCell } from 'design/DataTable'; -import { UserGroup } from 'teleport/services/userGroups'; - -import { ListProps, StyledTable, renderActionCell } from './ResourceList'; - -export function UserGroups(props: ListProps & { userGroups: UserGroup[] }) { - const { - userGroups = [], - addedResources, - customSort, - requestStarted, - onLabelClick, - addOrRemoveResource, - } = props; - - return ( - {friendlyName || name}, - }, - { - key: 'description', - headerText: 'Description', - isSortable: true, - }, - { - key: 'labels', - headerText: 'Labels', - render: ({ labels }) => ( - - ), - }, - { - altKey: 'action-btn', - render: agent => - renderActionCell( - Boolean(addedResources.user_group[agent.name]), - requestStarted, - () => - addOrRemoveResource( - 'user_group', - agent.name, - agent.friendlyName - ) - ), - }, - ]} - emptyText="No Results Found" - customSort={customSort} - disableFilter - /> - ); -} From ce2df571354063cefe5dd33818193c6aa22673b3 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 21 Nov 2024 12:11:18 +0100 Subject: [PATCH 2/4] Refactor `Roles` component --- .../NewRequest/ResourceList/Roles.tsx | 55 -------------- .../NewRequest/Roles/Roles.story.tsx | 53 ++++++++++++++ .../NewRequest/Roles/Roles.test.tsx | 70 ++++++++++++++++++ .../AccessRequests/NewRequest/Roles/Roles.tsx | 71 +++++++++++++++++++ .../{ResourceList => Roles}/index.ts | 2 +- .../AccessRequests/NewRequest/index.ts | 2 +- 6 files changed, 196 insertions(+), 57 deletions(-) delete mode 100644 web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Roles.tsx create mode 100644 web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.story.tsx create mode 100644 web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.test.tsx create mode 100644 web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.tsx rename web/packages/shared/components/AccessRequests/NewRequest/{ResourceList => Roles}/index.ts (93%) diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Roles.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Roles.tsx deleted file mode 100644 index 5ec6e78f95ff1..0000000000000 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Roles.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Teleport - * Copyright (C) 2024 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import React from 'react'; - -import { ListProps, StyledTable, renderActionCell } from './ResourceList'; - -export function Roles(props: ListProps & { roles: string[] }) { - const { - roles = [], - addedResources, - addOrRemoveResource, - requestStarted, - } = props; - - return ( - ({ role }))} - pagination={{ pagerPosition: 'top', pageSize: 10 }} - isSearchable={true} - columns={[ - { - key: 'role', - headerText: 'Role Name', - isSortable: true, - }, - { - altKey: 'action-btn', - render: ({ role }) => - renderActionCell( - Boolean(addedResources.role[role]), - requestStarted, - () => addOrRemoveResource('role', role) - ), - }, - ]} - emptyText="No Requestable Roles Found" - /> - ); -} diff --git a/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.story.tsx b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.story.tsx new file mode 100644 index 0000000000000..085bba838ab97 --- /dev/null +++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.story.tsx @@ -0,0 +1,53 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { useState } from 'react'; + +import { Roles } from './Roles'; + +export default { + title: 'Shared/AccessRequests/Roles', +}; + +export function Default() { + const [requested, setRequested] = useState(new Set()); + return ( + + setRequested(prev => { + const newSet = new Set(prev); + newSet.has(role) ? newSet.delete(role) : newSet.add(role); + return newSet; + }) + } + /> + ); +} + +export function Disabled() { + return ( + {}} + disabled + /> + ); +} diff --git a/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.test.tsx b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.test.tsx new file mode 100644 index 0000000000000..a49da62b82ed4 --- /dev/null +++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.test.tsx @@ -0,0 +1,70 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { within, screen } from '@testing-library/react'; +import { render } from 'design/utils/testing'; + +import { Roles } from './Roles'; + +test('renders requestable roles with a request option', async () => { + render( + {}} + /> + ); + + const row = await screen.findByRole('row', { + name: /editor/i, + }); + + expect( + await within(row).findByRole('button', { + name: /request access/i, + }) + ).toBeVisible(); +}); + +test('renders requested roles with a remove option and requestable roles with an add option', async () => { + render( + {}} + /> + ); + + const rowEditor = await screen.findByRole('row', { + name: /editor/i, + }); + expect( + await within(rowEditor).findByRole('button', { + name: /remove/i, + }) + ).toBeVisible(); + + const rowAccess = await screen.findByRole('row', { + name: /access/i, + }); + expect( + await within(rowAccess).findByRole('button', { + name: /add to request/i, + }) + ).toBeVisible(); +}); diff --git a/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.tsx b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.tsx new file mode 100644 index 0000000000000..69b56fda3ce93 --- /dev/null +++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.tsx @@ -0,0 +1,71 @@ +/** + * Teleport + * Copyright (C) 2024 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import Table, { Cell } from 'design/DataTable'; +import { ButtonPrimary, ButtonBorder } from 'design/Button'; + +export function Roles(props: { + requestable: string[]; + requested: Set; + onToggleRole(role: string): void; + /** Disables buttons.*/ + disabled?: boolean; +}) { + const addToRequestText = props.requested.size + ? '+ Add to Request' + : '+ Request Access'; + + return ( + ({ role }))} + pagination={{ pagerPosition: 'top', pageSize: 10 }} + isSearchable={true} + columns={[ + { + key: 'role', + headerText: 'Role Name', + isSortable: true, + }, + { + altKey: 'action', + render: ({ role }) => { + const isAdded = props.requested.has(role); + const commonProps = { + disabled: props.disabled, + width: '137px', + size: 'small' as const, + onClick: () => props.onToggleRole(role), + }; + return ( + + {isAdded ? ( + Remove + ) : ( + + {addToRequestText} + + )} + + ); + }, + }, + ]} + emptyText="No Requestable Roles Found" + /> + ); +} diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/index.ts b/web/packages/shared/components/AccessRequests/NewRequest/Roles/index.ts similarity index 93% rename from web/packages/shared/components/AccessRequests/NewRequest/ResourceList/index.ts rename to web/packages/shared/components/AccessRequests/NewRequest/Roles/index.ts index c00c1256c7617..0a6e77dddc722 100644 --- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/index.ts +++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/index.ts @@ -16,4 +16,4 @@ * along with this program. If not, see . */ -export { ResourceList } from './ResourceList'; +export { Roles } from './Roles'; diff --git a/web/packages/shared/components/AccessRequests/NewRequest/index.ts b/web/packages/shared/components/AccessRequests/NewRequest/index.ts index 61c58603809d1..dc3c2614a9d7d 100644 --- a/web/packages/shared/components/AccessRequests/NewRequest/index.ts +++ b/web/packages/shared/components/AccessRequests/NewRequest/index.ts @@ -17,7 +17,7 @@ */ export * from './RequestCheckout'; -export * from './ResourceList'; +export * from './Roles'; export type { ResourceMap, RequestableResourceKind } from './resource'; export { getEmptyResourceState } from './resource'; export { isKubeClusterWithNamespaces } from './kube'; From e86193eafc6137e5d08d5ff0f9ad114d847d306d Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 21 Nov 2024 12:58:39 +0100 Subject: [PATCH 3/4] Clean up new request screen in Connect --- .../NewRequest/NewRequest.tsx | 97 ++++++------------- 1 file changed, 29 insertions(+), 68 deletions(-) diff --git a/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx b/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx index 8cecd1388319a..39653a32f0b03 100644 --- a/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx +++ b/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx @@ -16,16 +16,13 @@ * along with this program. If not, see . */ -import styled from 'styled-components'; -import { Alert, Box, Link } from 'design'; +import { Link, Flex, Box } from 'design'; +import { Info } from 'design/Alert'; import { ShowResources } from 'gen-proto-ts/teleport/lib/teleterm/v1/cluster_pb'; -import { - ResourceList, - ResourceMap, -} from 'shared/components/AccessRequests/NewRequest'; -import { PendingAccessRequest } from 'teleterm/ui/services/workspacesService/accessRequestsService'; +import { useAsync } from 'shared/hooks/useAsync'; +import { Roles } from 'shared/components/AccessRequests/NewRequest'; import { useWorkspaceContext } from 'teleterm/ui/Documents'; import { useAppContext } from 'teleterm/ui/appContextProvider'; @@ -51,7 +48,6 @@ export function NewRequest() { const loggedInUser = rootCluster?.loggedInUser; const requestableRoles = loggedInUser?.requestableRoles || []; const addedResources = accessRequestsService.getPendingAccessRequest(); - const requestStarted = accessRequestsService.getAddedItemsCount() > 0; function openClusterDocument() { const doc = documentsService.createClusterDocument({ @@ -64,27 +60,31 @@ export function NewRequest() { const doesUnifiedResourcesShowBothAccessibleAndRequestableResources = rootCluster?.showResources === ShowResources.REQUESTABLE; + const [addOrRemoveRoleAttempt, addOrRemoveRole] = useAsync((role: string) => + accessRequestsService.addOrRemoveRole(role) + ); + return ( - - - {}} - addedResources={toResourceMap(addedResources)} - addOrRemoveResource={(kind, resourceId) => { - // We can only have roles here. - if (kind === 'role') { - accessRequestsService.addOrRemoveRole(resourceId); - } - }} - requestableRoles={requestableRoles} - disableRows={false} + + + void addOrRemoveRole(role)} + disabled={addOrRemoveRoleAttempt.status === 'processing'} /> - - + + To request access to a resource, go to the{' '} {/*TODO: Improve ButtonLink to look more like a text, then use it instead of the Link. */} Show requestable resources.'} - - + + ); } - -const Layout = styled(Box)` - flex-direction: column; - display: flex; - flex: 1; - max-width: 1248px; - - &::after { - content: ' '; - padding-bottom: 24px; - } -`; - -const StyledMain = styled.div` - display: flex; - flex-direction: column; - flex: 1; -`; - -function toResourceMap(request: PendingAccessRequest): ResourceMap { - const resourceMap: ResourceMap = { - user_group: {}, - windows_desktop: {}, - role: {}, - kube_cluster: {}, - node: {}, - db: {}, - app: {}, - saml_idp_service_provider: {}, - namespace: {}, - }; - if (request.kind === 'role') { - request.roles.forEach(role => { - resourceMap.role[role] = role; - }); - } - - return resourceMap; -} From 5b43a0def251e24c19eee0a16da0698b0def9722 Mon Sep 17 00:00:00 2001 From: Grzegorz Zdunek Date: Thu, 21 Nov 2024 17:11:12 +0100 Subject: [PATCH 4/4] Move "the" to the anchor --- .../src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx b/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx index 39653a32f0b03..2abff7de4834b 100644 --- a/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx +++ b/web/packages/teleterm/src/ui/DocumentAccessRequests/NewRequest/NewRequest.tsx @@ -85,7 +85,7 @@ export function NewRequest() { /> - To request access to a resource, go to the{' '} + To request access to a resource, go to{' '} {/*TODO: Improve ButtonLink to look more like a text, then use it instead of the Link. */} - resources view + the resources view {' '} {doesUnifiedResourcesShowBothAccessibleAndRequestableResources ? 'or find it in the search bar.'