Skip to content

Commit

Permalink
[v17] Miscellaneous role editor UI tweaks (#50205)
Browse files Browse the repository at this point in the history
* Miscellaneous role editor UI tweaks

* lint
  • Loading branch information
bl-nero authored Dec 13, 2024
1 parent 654f1e8 commit ab16870
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 24 deletions.
35 changes: 34 additions & 1 deletion web/packages/teleport/src/Roles/RoleEditor/RoleEditor.story.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 React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { StoryObj } from '@storybook/react';
import { delay, http, HttpResponse } from 'msw';
import { Info } from 'design/Alert';
Expand All @@ -34,6 +34,8 @@ import { withDefaults } from './withDefaults';
import { RoleEditor } from './RoleEditor';
import { RoleEditorDialog } from './RoleEditorDialog';

const defaultIsPolicyEnabled = cfg.isPolicyEnabled;

export default {
title: 'Teleport/Roles/Role Editor',
decorators: [
Expand All @@ -42,6 +44,12 @@ export default {
if (parameters.acl) {
ctx.storeUser.getRoleAccess = () => parameters.acl;
}
useEffect(() => {
// Clean up
return () => {
cfg.isPolicyEnabled = defaultIsPolicyEnabled;
};
}, []);
return (
<TeleportContextProvider ctx={ctx}>
<Flex flexDirection="column" width="700px" height="800px">
Expand Down Expand Up @@ -290,6 +298,31 @@ export const Dialog: StoryObj = {
},
};

export const DialogWithPolicyEnabled: StoryObj = {
render() {
cfg.isPolicyEnabled = true;
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
12 changes: 6 additions & 6 deletions web/packages/teleport/src/Roles/RoleEditor/RoleEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,34 +126,34 @@ test('rendering and switching tabs for a non-standard role', async () => {
);
expect(getYamlEditorTab()).toHaveAttribute('aria-selected', 'true');
expect(fromFauxYaml(await getTextEditorContents())).toEqual(originalRole);
expect(screen.getByRole('button', { name: 'Update Role' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();

await user.click(getStandardEditorTab());
expect(
screen.getByRole('button', { name: 'Reset to Standard Settings' })
).toBeVisible();
expect(screen.getByLabelText('Role Name')).toHaveValue('some-role');
expect(screen.getByLabelText('Description')).toHaveValue('');
expect(screen.getByRole('button', { name: 'Update Role' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();

await user.click(getYamlEditorTab());
expect(fromFauxYaml(await getTextEditorContents())).toEqual(originalRole);
expect(screen.getByRole('button', { name: 'Update Role' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();

// Switch once again, reset to standard
await user.click(getStandardEditorTab());
expect(screen.getByRole('button', { name: 'Update Role' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();
await user.click(
screen.getByRole('button', { name: 'Reset to Standard Settings' })
);
expect(screen.getByRole('button', { name: 'Update Role' })).toBeEnabled();
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeEnabled();
await user.type(screen.getByLabelText('Description'), 'some description');

await user.click(getYamlEditorTab());
const editorContents = fromFauxYaml(await getTextEditorContents());
expect(editorContents.metadata.description).toBe('some description');
expect(editorContents.spec.deny).toEqual({});
expect(screen.getByRole('button', { name: 'Update Role' })).toBeEnabled();
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeEnabled();
});

test('switching tabs triggers validation', async () => {
Expand Down
44 changes: 28 additions & 16 deletions web/packages/teleport/src/Roles/RoleEditor/RoleEditorAdapter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { yamlService } from 'teleport/services/yaml';
import { YamlSupportedResourceKind } from 'teleport/services/yaml/types';
import { ButtonLockedFeature } from 'teleport/components/ButtonLockedFeature';

import cfg from 'teleport/config';

import { RoleEditor } from './RoleEditor';
import tagpromo from './tagpromo.png';

Expand Down Expand Up @@ -111,7 +113,7 @@ export function RoleEditorAdapter({
<Flex flex="1" alignItems="center" justifyContent="center" m={3}>
{/* Same width as promo image + border */}
<Box maxWidth={promoImageWidth + 2 * 2} minWidth={300}>
<H1 mb={2}>Teleport Policy</H1>
<H1 mb={2}>Coming soon: Teleport Policy saves you from mistakes</H1>
<Flex mb={4} gap={4} flexWrap="wrap" justifyContent="space-between">
<Box flex="1" minWidth="30ch">
<P>
Expand All @@ -121,17 +123,21 @@ export function RoleEditorAdapter({
</P>
</Box>
<Flex flex="0 0 auto" alignItems="start">
<ButtonLockedFeature noIcon py={0} width={undefined}>
Contact Sales
</ButtonLockedFeature>
<ButtonSecondary
as="a"
href="https://goteleport.com/platform/policy/"
target="_blank"
ml={2}
>
Learn More
</ButtonSecondary>
{!cfg.isPolicyEnabled && (
<>
<ButtonLockedFeature noIcon py={0} width={undefined}>
Contact Sales
</ButtonLockedFeature>
<ButtonSecondary
as="a"
href="https://goteleport.com/platform/policy/"
target="_blank"
ml={2}
>
Learn More
</ButtonSecondary>
</>
)}
</Flex>
</Flex>
<Flex
Expand All @@ -146,7 +152,12 @@ export function RoleEditorAdapter({
>
<Image src={tagpromo} width="100%" />
</Box>
<StepSlider flows={promoFlows} currFlow="default" />
<StepSlider
flows={promoFlows}
currFlow={
resources.status === 'creating' ? 'creating' : 'updating'
}
/>
</Flex>
</Box>
</Flex>
Expand All @@ -157,10 +168,11 @@ export function RoleEditorAdapter({
const promoImageWidth = 782;

const promoFlows = {
default: [PromoPanel1, PromoPanel2],
creating: [VisualizeAccessPathsPanel, VisualizeDiffPanel],
updating: [VisualizeDiffPanel, VisualizeAccessPathsPanel],
};

function PromoPanel1(props: StepComponentProps) {
function VisualizeAccessPathsPanel(props: StepComponentProps) {
return (
<PromoPanel
{...props}
Expand All @@ -176,7 +188,7 @@ function PromoPanel1(props: StepComponentProps) {
);
}

function PromoPanel2(props: StepComponentProps) {
function VisualizeDiffPanel(props: StepComponentProps) {
return (
<PromoPanel
{...props}
Expand Down
2 changes: 1 addition & 1 deletion web/packages/teleport/src/Roles/RoleEditor/Shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const EditorSaveCancelButton = ({
(!isEditing && !roleAccess.create)
}
>
{isEditing ? 'Update' : 'Create'} Role
{isEditing ? 'Save Changes' : 'Create Role'}
</ButtonPrimary>
</HoverTooltip>
</Box>
Expand Down
44 changes: 44 additions & 0 deletions web/packages/teleport/src/Roles/Roles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ import { P } from 'design/Text/Text';
import { MissingPermissionsTooltip } from 'shared/components/MissingPermissionsTooltip';
import { HoverTooltip } from 'design/Tooltip';

import {
Notification,
NotificationItem,
NotificationSeverity,
} from 'shared/components/Notification';

import styled from 'styled-components';

import {
FeatureBox,
FeatureHeader,
Expand Down Expand Up @@ -52,6 +60,18 @@ const useNewRoleEditor = storageService.getUseNewRoleEditor();
export function Roles(props: State) {
const { remove, create, update, fetch, rolesAcl } = props;
const [search, setSearch] = useState('');
const [notifications, setNotifications] = useState<NotificationItem[]>([]);

function addNotification(content: string, severity: NotificationSeverity) {
setNotifications(notifications => [
...notifications,
{ id: crypto.randomUUID(), content, severity },
]);
}

function removeNotification(id: string) {
setNotifications(n => n.filter(item => item.id !== id));
}

const serverSidePagination = useServerSidePagination<RoleResource>({
pageSize: 20,
Expand All @@ -76,6 +96,13 @@ export function Roles(props: State) {
? create(role)
: update(resources.item.name, role));

addNotification(
resources.status === 'creating'
? `Role ${response.name} has been created`
: `Role ${response.name} has been updated`,
'success'
);

if (useNewRoleEditor) {
// We don't really disregard anything, since we already saved the role;
// this is done just to hide the new editor.
Expand Down Expand Up @@ -246,6 +273,17 @@ export function Roles(props: State) {
onDelete={handleDelete}
/>
)}

<NotificationContainer>
{notifications.map(item => (
<Notification
mb={3}
key={item.id}
item={item}
onRemove={() => removeNotification(item.id)}
/>
))}
</NotificationContainer>
</FeatureBox>
);
}
Expand All @@ -265,3 +303,9 @@ function Directions() {
</>
);
}

const NotificationContainer = styled.div`
position: absolute;
bottom: ${props => props.theme.space[2]}px;
right: ${props => props.theme.space[5]}px;
`;

0 comments on commit ab16870

Please sign in to comment.