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

[v17] Miscellaneous role editor UI tweaks #50205

Merged
merged 2 commits into from
Dec 13, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
`;
Loading