Skip to content

Commit c98489b

Browse files
committed
WebDiscover: Allow setting labels when enrolling single web application (#50853)
* Allow labels for generic add web app flow * Update test
1 parent 0a538d2 commit c98489b

File tree

6 files changed

+142
-51
lines changed

6 files changed

+142
-51
lines changed

web/packages/teleport/src/Apps/AddApp/AddApp.story.tsx

+41-7
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,50 @@
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19+
import { useState } from 'react';
20+
21+
import { JoinToken } from 'teleport/services/joinToken';
22+
1923
import { AddApp } from './AddApp';
2024

2125
export default {
22-
title: 'Teleport/Apps/Add',
26+
title: 'Teleport/Discover/Application/Web',
2327
};
2428

25-
export const Created = () => (
26-
<AddApp {...props} attempt={{ status: 'success' }} />
27-
);
29+
export const CreatedWithoutLabels = () => {
30+
const [token, setToken] = useState<JoinToken>();
31+
32+
return (
33+
<AddApp
34+
{...props}
35+
attempt={{ status: 'success' }}
36+
token={token}
37+
createToken={() => {
38+
setToken(props.token);
39+
return Promise.resolve(true);
40+
}}
41+
/>
42+
);
43+
};
44+
45+
export const CreatedWithLabels = () => {
46+
const [token, setToken] = useState<JoinToken>();
2847

29-
export const Loaded = () => {
30-
return <AddApp {...props} />;
48+
return (
49+
<AddApp
50+
{...props}
51+
attempt={{ status: 'success' }}
52+
labels={[
53+
{ name: 'env', value: 'staging' },
54+
{ name: 'fruit', value: 'apple' },
55+
]}
56+
token={token}
57+
createToken={() => {
58+
setToken(props.token);
59+
return Promise.resolve(true);
60+
}}
61+
/>
62+
);
3163
};
3264

3365
export const Processing = () => (
@@ -72,8 +104,10 @@ const props = {
72104
createJoinToken: () => Promise.resolve(null),
73105
version: '5.0.0-dev',
74106
reset: () => null,
107+
labels: [],
108+
setLabels: () => null,
75109
attempt: {
76-
status: '',
110+
status: 'success',
77111
statusText: '',
78112
} as any,
79113
token: {

web/packages/teleport/src/Apps/AddApp/AddApp.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export function AddApp({
4444
setAutomatic,
4545
isAuthTypeLocal,
4646
token,
47+
labels,
48+
setLabels,
4749
}: State & Props) {
4850
return (
4951
<Dialog
@@ -82,6 +84,8 @@ export function AddApp({
8284
onCreate={createToken}
8385
attempt={attempt}
8486
token={token}
87+
labels={labels}
88+
setLabels={setLabels}
8589
/>
8690
)}
8791
{!automatic && (

web/packages/teleport/src/Apps/AddApp/Automatically.test.tsx

+18-5
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19-
import { act } from '@testing-library/react';
20-
2119
import { fireEvent, render, screen } from 'design/utils/testing';
2220

2321
import { Automatically, createAppBashCommand } from './Automatically';
@@ -33,12 +31,14 @@ test('render command only after form submit', async () => {
3331
roles: [],
3432
content: '',
3533
};
36-
render(
34+
const { rerender } = render(
3735
<Automatically
38-
token={token}
3936
attempt={{ status: 'success' }}
4037
onClose={() => {}}
4138
onCreate={() => Promise.resolve(true)}
39+
labels={[]}
40+
setLabels={() => null}
41+
token={null}
4242
/>
4343
);
4444

@@ -56,8 +56,21 @@ test('render command only after form submit', async () => {
5656
target: { value: 'https://gravitational.com' },
5757
});
5858

59+
rerender(
60+
<Automatically
61+
attempt={{ status: 'success' }}
62+
onClose={() => {}}
63+
onCreate={() => Promise.resolve(true)}
64+
labels={[]}
65+
setLabels={() => null}
66+
token={token}
67+
/>
68+
);
69+
5970
// click button
60-
act(() => screen.getByRole('button', { name: /Generate Script/i }).click());
71+
fireEvent.click(screen.getByRole('button', { name: /Generate Script/i }));
72+
73+
await screen.findByText(/Regenerate Script/i);
6174

6275
// after form submission should show the command
6376
cmd = createAppBashCommand(token.id, 'app-name', 'https://gravitational.com');

web/packages/teleport/src/Apps/AddApp/Automatically.tsx

+35-35
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { KeyboardEvent, useEffect, useState } from 'react';
2020

2121
import {
2222
Alert,
23+
Box,
2324
ButtonPrimary,
2425
ButtonSecondary,
2526
Flex,
@@ -33,50 +34,40 @@ import { Attempt } from 'shared/hooks/useAttemptNext';
3334

3435
import TextSelectCopy from 'teleport/components/TextSelectCopy';
3536
import cfg from 'teleport/config';
37+
import { LabelsCreater } from 'teleport/Discover/Shared';
38+
import { ResourceLabelTooltip } from 'teleport/Discover/Shared/ResourceLabelTooltip';
39+
import { ResourceLabel } from 'teleport/services/agents';
3640

3741
import { State } from './useAddApp';
3842

3943
export function Automatically(props: Props) {
40-
const { onClose, attempt, token } = props;
44+
const { onClose, attempt, token, labels, setLabels } = props;
4145

4246
const [name, setName] = useState('');
4347
const [uri, setUri] = useState('');
4448
const [cmd, setCmd] = useState('');
4549

4650
useEffect(() => {
47-
if (name && uri) {
51+
if (name && uri && token) {
4852
const cmd = createAppBashCommand(token.id, name, uri);
4953
setCmd(cmd);
5054
}
5155
}, [token]);
5256

53-
function handleRegenerate(validator: Validator) {
57+
function onGenerateScript(validator: Validator) {
5458
if (!validator.validate()) {
5559
return;
5660
}
5761

5862
props.onCreate(name, uri);
5963
}
6064

61-
function handleGenerate(validator: Validator) {
62-
if (!validator.validate()) {
63-
return;
64-
}
65-
66-
const cmd = createAppBashCommand(token.id, name, uri);
67-
setCmd(cmd);
68-
}
69-
7065
function handleEnterPress(
7166
e: KeyboardEvent<HTMLInputElement>,
7267
validator: Validator
7368
) {
7469
if (e.key === 'Enter') {
75-
if (cmd) {
76-
handleRegenerate(validator);
77-
} else {
78-
handleGenerate(validator);
79-
}
70+
onGenerateScript(validator);
8071
}
8172
}
8273

@@ -96,6 +87,7 @@ export function Automatically(props: Props) {
9687
mr="3"
9788
onKeyPress={e => handleEnterPress(e, validator)}
9889
onChange={e => setName(e.target.value.toLowerCase())}
90+
disabled={attempt.status === 'processing'}
9991
/>
10092
<FieldInput
10193
rule={requiredAppUri}
@@ -105,8 +97,25 @@ export function Automatically(props: Props) {
10597
placeholder="https://localhost:4000"
10698
onKeyPress={e => handleEnterPress(e, validator)}
10799
onChange={e => setUri(e.target.value)}
100+
disabled={attempt.status === 'processing'}
108101
/>
109102
</Flex>
103+
<Box mt={-3} mb={3}>
104+
<Flex alignItems="center" gap={1} mb={2} mt={4}>
105+
<Text bold>Add Labels (Optional)</Text>
106+
<ResourceLabelTooltip
107+
toolTipPosition="top"
108+
resourceKind="app"
109+
/>
110+
</Flex>
111+
<LabelsCreater
112+
labels={labels}
113+
setLabels={setLabels}
114+
isLabelOptional={true}
115+
disableBtns={attempt.status === 'processing'}
116+
noDuplicateKey={true}
117+
/>
118+
</Box>
110119
{!cmd && (
111120
<Text mb="3">
112121
Teleport can automatically set up application access. Provide
@@ -136,24 +145,13 @@ export function Automatically(props: Props) {
136145
)}
137146
</DialogContent>
138147
<DialogFooter>
139-
{!cmd && (
140-
<ButtonPrimary
141-
mr="3"
142-
disabled={attempt.status === 'processing'}
143-
onClick={() => handleGenerate(validator)}
144-
>
145-
Generate Script
146-
</ButtonPrimary>
147-
)}
148-
{cmd && (
149-
<ButtonPrimary
150-
mr="3"
151-
disabled={attempt.status === 'processing'}
152-
onClick={() => handleRegenerate(validator)}
153-
>
154-
Regenerate
155-
</ButtonPrimary>
156-
)}
148+
<ButtonPrimary
149+
mr="3"
150+
disabled={attempt.status === 'processing'}
151+
onClick={() => onGenerateScript(validator)}
152+
>
153+
{cmd ? 'Regenerate Script' : 'Generate Script'}
154+
</ButtonPrimary>
157155
<ButtonSecondary
158156
disabled={attempt.status === 'processing'}
159157
onClick={onClose}
@@ -271,4 +269,6 @@ type Props = {
271269
onCreate(name: string, uri: string): Promise<any>;
272270
token: State['token'];
273271
attempt: Attempt;
272+
labels: ResourceLabel[];
273+
setLabels(r: ResourceLabel[]): void;
274274
};

web/packages/teleport/src/Apps/AddApp/useAddApp.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { useEffect, useState } from 'react';
2020

2121
import useAttempt from 'shared/hooks/useAttemptNext';
2222

23+
import { ResourceLabel } from 'teleport/services/agents';
2324
import type { JoinToken } from 'teleport/services/joinToken';
2425
import TeleportContext from 'teleport/teleportContext';
2526

@@ -31,14 +32,27 @@ export default function useAddApp(ctx: TeleportContext) {
3132
const isEnterprise = ctx.isEnterprise;
3233
const [automatic, setAutomatic] = useState(isEnterprise);
3334
const [token, setToken] = useState<JoinToken>();
35+
const [labels, setLabels] = useState<ResourceLabel[]>([]);
3436

3537
useEffect(() => {
36-
createToken();
37-
}, []);
38+
// We don't want to create token on first render
39+
// which defaults to the automatic tab because
40+
// user may want to add labels.
41+
if (!automatic) {
42+
setLabels([]);
43+
// When switching to manual tab, token can be re-used
44+
// if token was already generated from automatic tab.
45+
if (!token) {
46+
createToken();
47+
}
48+
}
49+
}, [automatic]);
3850

3951
function createToken() {
4052
return run(() =>
41-
ctx.joinTokenService.fetchJoinToken({ roles: ['App'] }).then(setToken)
53+
ctx.joinTokenService
54+
.fetchJoinToken({ roles: ['App'], suggestedLabels: labels })
55+
.then(setToken)
4256
);
4357
}
4458

@@ -52,6 +66,8 @@ export default function useAddApp(ctx: TeleportContext) {
5266
isAuthTypeLocal,
5367
isEnterprise,
5468
token,
69+
labels,
70+
setLabels,
5571
};
5672
}
5773

web/packages/teleport/src/Discover/Shared/ResourceLabelTooltip/ResourceLabelTooltip.tsx

+25-1
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,36 @@ export function ResourceLabelTooltip({
3737
resourceKind,
3838
toolTipPosition,
3939
}: {
40-
resourceKind: 'server' | 'eks' | 'rds' | 'kube' | 'db';
40+
resourceKind: 'server' | 'eks' | 'rds' | 'kube' | 'db' | 'app';
4141
toolTipPosition?: Position;
4242
}) {
4343
let tip;
4444

4545
switch (resourceKind) {
46+
case 'app': {
47+
tip = (
48+
<>
49+
Labels allow you to do the following:
50+
<Ul>
51+
<li>
52+
Filter applications by labels when using tsh, tctl, or the web UI.
53+
</li>
54+
<li>
55+
Restrict access to this application with{' '}
56+
<Link
57+
target="_blank"
58+
href="https://goteleport.com/docs/enroll-resources/application-access/controls/"
59+
>
60+
Teleport RBAC
61+
</Link>
62+
. Only roles with <MarkInverse>app_labels</MarkInverse> that match
63+
these labels will be allowed to access this application.
64+
</li>
65+
</Ul>
66+
</>
67+
);
68+
break;
69+
}
4670
case 'server': {
4771
tip = (
4872
<>

0 commit comments

Comments
 (0)