Skip to content

Commit

Permalink
Move the new role editor to a full-screen dialog (#49881)
Browse files Browse the repository at this point in the history
* Add admin rule tab to the role editor

Also tightens some constraints about standard role editor conformance.

* Add a way to delete an admin rule

* Add the options panel to role editor

* Move RoleEditorAdapter to a separate file

* Move the role editor to a full-screen dialog

* Review

* Review

* Review

* Lint

* Review
  • Loading branch information
bl-nero authored Dec 10, 2024
1 parent cfe482b commit 7939dcb
Show file tree
Hide file tree
Showing 10 changed files with 594 additions and 245 deletions.
14 changes: 10 additions & 4 deletions web/packages/teleport/src/Roles/RoleEditor/EditorHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
*/

import React from 'react';
import { Flex, ButtonText, H2, Indicator, Box } from 'design';
import { Flex, H2, Indicator, Box, ButtonIcon } from 'design';
import { HoverTooltip } from 'design/Tooltip';
import { Trash } from 'design/Icon';

import { Cross, Trash } from 'design/Icon';

import useTeleport from 'teleport/useTeleport';
import { Role } from 'teleport/services/resources';
Expand All @@ -35,6 +36,7 @@ export const EditorHeader = ({
isProcessing,
standardEditorId,
yamlEditorId,
onClose,
}: {
role?: Role;
onDelete(): void;
Expand All @@ -43,6 +45,7 @@ export const EditorHeader = ({
isProcessing: boolean;
standardEditorId: string;
yamlEditorId: string;
onClose(): void;
}) => {
const ctx = useTeleport();
const isCreating = !role;
Expand All @@ -51,6 +54,9 @@ export const EditorHeader = ({

return (
<Flex alignItems="center" mb={3} gap={2}>
<ButtonIcon aria-label="Close" onClick={onClose}>
<Cross size="small" />
</ButtonIcon>
<Box flex="1">
<H2>
{isCreating
Expand All @@ -77,14 +83,14 @@ export const EditorHeader = ({
: 'You do not have access to delete a role'
}
>
<ButtonText
<ButtonIcon
onClick={onDelete}
disabled={!hasDeleteAccess}
data-testid="delete"
p={1}
>
<Trash size="medium" />
</ButtonText>
</ButtonIcon>
</HoverTooltip>
)}
</Flex>
Expand Down
30 changes: 28 additions & 2 deletions web/packages/teleport/src/Roles/RoleEditor/RoleEditor.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from 'react';
import React, { useState } from 'react';
import { StoryObj } from '@storybook/react';
import { delay, http, HttpResponse } from 'msw';
import { Info } from 'design/Alert';
import Flex from 'design/Flex';
import { ButtonPrimary } from 'design/Button';

import { createTeleportContext } from 'teleport/mocks/contexts';
import TeleportContextProvider from 'teleport/TeleportContextProvider';
import cfg from 'teleport/config';
import { YamlSupportedResourceKind } from 'teleport/services/yaml/types';

import { Access } from 'teleport/services/user';
import useResources from 'teleport/components/useResources';

import { withDefaults } from './withDefaults';
import { RoleEditor } from './RoleEditor';
import { RoleEditorDialog } from './RoleEditorDialog';

export default {
title: 'Teleport/Roles/Role Editor',
Expand Down Expand Up @@ -264,6 +266,30 @@ export const noAccess: StoryObj = {
},
};

export const Dialog: StoryObj = {
render() {
const [open, setOpen] = useState(false);
const resources = useResources([], {});
return (
<>
<ButtonPrimary onClick={() => setOpen(true)}>Open</ButtonPrimary>
<RoleEditorDialog
resources={resources}
open={open}
onClose={() => setOpen(false)}
onSave={async () => setOpen(false)}
onDelete={async () => setOpen(false)}
/>
</>
);
},
parameters: {
msw: {
handlers: [yamlifyHandler, parseHandler],
},
},
};

const dummyRoleYaml = `kind: role
metadata:
name: dummy-role
Expand Down
70 changes: 42 additions & 28 deletions web/packages/teleport/src/Roles/RoleEditor/RoleEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { Alert, Flex } from 'design';
import { Alert, Box, Flex } from 'design';
import React, { useId, useState } from 'react';
import { useAsync } from 'shared/hooks/useAsync';

Expand All @@ -27,6 +27,8 @@ import { yamlService } from 'teleport/services/yaml';
import { YamlSupportedResourceKind } from 'teleport/services/yaml/types';
import { CaptureEvent, userEventService } from 'teleport/services/userEvent';

import DeleteRole from '../DeleteRole';

import {
roleEditorModelToRole,
newRole,
Expand All @@ -47,7 +49,7 @@ export type RoleEditorProps = {
originalRole?: RoleWithYaml;
onCancel?(): void;
onSave?(r: Partial<RoleWithYaml>): Promise<void>;
onDelete?(): void;
onDelete?(): Promise<void>;
};

/**
Expand Down Expand Up @@ -88,6 +90,8 @@ export const RoleEditor = ({
standardModel.roleModel.requiresReset ? EditorTab.Yaml : EditorTab.Standard
);

const [deleting, setDeleting] = useState(false);

// Converts YAML representation to a standard editor model.
const [parseAttempt, parseYaml] = useAsync(async () => {
const parsedRole = await yamlService.parse<Role>(
Expand Down Expand Up @@ -181,32 +185,35 @@ export const RoleEditor = ({
<Validation>
{({ validator }) => (
<Flex flexDirection="column" flex="1">
<EditorHeader
role={originalRole?.object}
onDelete={onDelete}
selectedEditorTab={selectedEditorTab}
onEditorTabChange={index => onTabChange(index, validator)}
isProcessing={isProcessing}
standardEditorId={standardEditorId}
yamlEditorId={yamlEditorId}
/>
{saveAttempt.status === 'error' && (
<Alert mt={3} dismissible>
{saveAttempt.statusText}
</Alert>
)}
{parseAttempt.status === 'error' && (
<Alert mt={3} dismissible>
{parseAttempt.statusText}
</Alert>
)}
{yamlifyAttempt.status === 'error' && (
<Alert mt={3} dismissible>
{yamlifyAttempt.statusText}
</Alert>
)}
<Box mt={3} mx={3}>
<EditorHeader
role={originalRole?.object}
onDelete={() => setDeleting(true)}
selectedEditorTab={selectedEditorTab}
onEditorTabChange={index => onTabChange(index, validator)}
isProcessing={isProcessing}
standardEditorId={standardEditorId}
yamlEditorId={yamlEditorId}
onClose={onCancel}
/>
{saveAttempt.status === 'error' && (
<Alert mt={3} dismissible>
{saveAttempt.statusText}
</Alert>
)}
{parseAttempt.status === 'error' && (
<Alert mt={3} dismissible>
{parseAttempt.statusText}
</Alert>
)}
{yamlifyAttempt.status === 'error' && (
<Alert mt={3} dismissible>
{yamlifyAttempt.statusText}
</Alert>
)}
</Box>
{selectedEditorTab === EditorTab.Standard && (
<div id={standardEditorId}>
<Flex flexDirection="column" flex="1" id={standardEditorId}>
<StandardEditor
originalRole={originalRole}
onSave={object => handleSave({ object })}
Expand All @@ -215,7 +222,7 @@ export const RoleEditor = ({
isProcessing={isProcessing}
onChange={setStandardModel}
/>
</div>
</Flex>
)}
{selectedEditorTab === EditorTab.Yaml && (
<Flex flexDirection="column" flex="1" id={yamlEditorId}>
Expand All @@ -229,6 +236,13 @@ export const RoleEditor = ({
/>
</Flex>
)}
{deleting && (
<DeleteRole
name={originalRole.object.metadata.name}
onClose={() => setDeleting(false)}
onDelete={onDelete}
/>
)}
</Flex>
)}
</Validation>
Expand Down
Loading

0 comments on commit 7939dcb

Please sign in to comment.