Skip to content

Commit a27e242

Browse files
author
Andrey Cheptsov
committed
[UI] Refine Connect wizard interactions
Address PR feedback by making Connect panels collapsible across run types, using Done to collapse wizard flows, and fixing task Open-step behavior when map_to_port is unset. Made-with: Cursor
1 parent ce4735a commit a27e242

7 files changed

Lines changed: 165 additions & 101 deletions

File tree

frontend/src/pages/Project/Details/Settings/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ export const ProjectSettings: React.FC = () => {
230230
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
231231
activeStepIndex={activeStepIndex}
232232
onSubmit={() => setIsExpandedCliSection(false)}
233-
submitButtonText="Dismiss"
233+
submitButtonText="Done"
234234
allowSkipTo={true}
235235
steps={[
236236
{

frontend/src/pages/Runs/Details/RunDetails/ConnectToRunWithDevEnvConfiguration/index.tsx

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,7 @@
11
import React, { FC } from 'react';
22
import { useTranslation } from 'react-i18next';
33

4-
import {
5-
Alert,
6-
Box,
7-
Button,
8-
Code,
9-
Container,
10-
ExpandableSection,
11-
Header,
12-
Popover,
13-
SpaceBetween,
14-
StatusIndicator,
15-
Tabs,
16-
Wizard,
17-
} from 'components';
4+
import { Alert, Box, Button, Code, ExpandableSection, Popover, SpaceBetween, StatusIndicator, Tabs, Wizard } from 'components';
185

196
import { copyToClipboard } from 'libs';
207

@@ -28,6 +15,7 @@ const PipInstallCommand = 'pip install dstack -U';
2815

2916
export const ConnectToRunWithDevEnvConfiguration: FC<{ run: IRun }> = ({ run }) => {
3017
const { t } = useTranslation();
18+
const [isExpandedConnectSection, setIsExpandedConnectSection] = React.useState(true);
3119

3220
const getAttachCommand = (runData: IRun) => {
3321
const attachCommand = `dstack attach ${runData.run_spec.run_name} --logs`;
@@ -62,9 +50,19 @@ export const ConnectToRunWithDevEnvConfiguration: FC<{ run: IRun }> = ({ run })
6250
const [configCliCommand, copyCliCommand] = useConfigProjectCliCommand({ projectName: run.project_name });
6351

6452
return (
65-
<Container>
66-
<Header variant="h2">Connect</Header>
67-
53+
<ExpandableSection
54+
variant="container"
55+
headerText="Connect"
56+
expanded={isExpandedConnectSection}
57+
onChange={({ detail }) => setIsExpandedConnectSection(detail.expanded)}
58+
headerActions={
59+
<Button
60+
iconName="script"
61+
variant={isExpandedConnectSection ? 'normal' : 'primary'}
62+
onClick={() => setIsExpandedConnectSection((prev) => !prev)}
63+
/>
64+
}
65+
>
6866
{run.status === 'running' && (
6967
<Wizard
7068
i18nStrings={{
@@ -78,15 +76,15 @@ export const ConnectToRunWithDevEnvConfiguration: FC<{ run: IRun }> = ({ run })
7876
}}
7977
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
8078
activeStepIndex={activeStepIndex}
81-
onSubmit={() => window.open(openInIDEUrl, '_blank')}
82-
submitButtonText={`Open in ${ideDisplayName}`}
79+
onSubmit={() => setIsExpandedConnectSection(false)}
80+
submitButtonText="Done"
8381
allowSkipTo
8482
steps={[
8583
{
8684
title: 'Attach',
85+
description: 'To access this run, first you need to attach to it.',
8786
content: (
8887
<SpaceBetween size="s">
89-
<Box>To access this run, first you need to attach to it.</Box>
9088
<div className={styles.codeWrapper}>
9189
<Code className={styles.code}>{attachCommand}</Code>
9290

@@ -275,6 +273,6 @@ export const ConnectToRunWithDevEnvConfiguration: FC<{ run: IRun }> = ({ run })
275273
<Alert type="info">Waiting for the run to start.</Alert>
276274
</SpaceBetween>
277275
)}
278-
</Container>
276+
</ExpandableSection>
279277
);
280278
};
Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,37 @@
11
import React, { FC } from 'react';
2+
import { useTranslation } from 'react-i18next';
23

3-
import { Alert, Box, Container, Header, Link, SpaceBetween } from 'components';
4+
import { Alert, Box, Button, ExpandableSection, Link, Popover, SpaceBetween, StatusIndicator, Wizard } from 'components';
45

6+
import { copyToClipboard } from 'libs';
57
import { getRunProbeStatuses } from 'libs/run';
68

79
import { getRunListItemServiceUrl } from '../../../List/helpers';
810

911
export const ConnectToServiceRun: FC<{ run: IRun }> = ({ run }) => {
12+
const { t } = useTranslation();
13+
const [isExpandedEndpointSection, setIsExpandedEndpointSection] = React.useState(true);
14+
const [activeStepIndex, setActiveStepIndex] = React.useState(0);
1015
const serviceUrl = getRunListItemServiceUrl(run);
1116
const probeStatuses = getRunProbeStatuses(run);
1217
const hasProbes = probeStatuses.length > 0;
1318
const allProbesReady = hasProbes && probeStatuses.every((s) => s === 'success');
1419
const serviceReady = run.status === 'running' && (!hasProbes || allProbesReady) && serviceUrl;
1520

1621
return (
17-
<Container>
18-
<Header variant="h2">Endpoint</Header>
19-
22+
<ExpandableSection
23+
variant="container"
24+
headerText="Connect"
25+
expanded={isExpandedEndpointSection}
26+
onChange={({ detail }) => setIsExpandedEndpointSection(detail.expanded)}
27+
headerActions={
28+
<Button
29+
iconName="script"
30+
variant={isExpandedEndpointSection ? 'normal' : 'primary'}
31+
onClick={() => setIsExpandedEndpointSection((prev) => !prev)}
32+
/>
33+
}
34+
>
2035
{run.status !== 'running' && (
2136
<SpaceBetween size="s">
2237
<Box />
@@ -32,16 +47,58 @@ export const ConnectToServiceRun: FC<{ run: IRun }> = ({ run }) => {
3247
)}
3348

3449
{serviceReady && (
35-
<SpaceBetween size="s">
36-
<Box />
37-
<Alert type="success">
38-
The service is ready at{' '}
39-
<Link href={serviceUrl} external>
40-
{serviceUrl}
41-
</Link>
42-
</Alert>
43-
</SpaceBetween>
50+
<Wizard
51+
i18nStrings={{
52+
stepNumberLabel: (stepNumber) => `Step ${stepNumber}`,
53+
collapsedStepsLabel: (stepNumber, stepsCount) => `Step ${stepNumber} of ${stepsCount}`,
54+
skipToButtonLabel: (step) => `Skip to ${step.title}`,
55+
navigationAriaLabel: 'Steps',
56+
previousButton: 'Previous',
57+
nextButton: 'Next',
58+
optional: 'required',
59+
}}
60+
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
61+
activeStepIndex={activeStepIndex}
62+
onSubmit={() => setIsExpandedEndpointSection(false)}
63+
submitButtonText="Done"
64+
allowSkipTo
65+
steps={[
66+
{
67+
title: 'Open',
68+
description: 'Open the service endpoint.',
69+
content: (
70+
<SpaceBetween size="s">
71+
<Alert
72+
type="info"
73+
action={
74+
<Popover
75+
dismissButton={false}
76+
position="top"
77+
size="small"
78+
triggerType="custom"
79+
content={<StatusIndicator type="success">{t('common.copied')}</StatusIndicator>}
80+
>
81+
<Button
82+
formAction="none"
83+
iconName="copy"
84+
variant="normal"
85+
onClick={() => copyToClipboard(serviceUrl)}
86+
/>
87+
</Popover>
88+
}
89+
>
90+
The service is ready at{' '}
91+
<Link href={serviceUrl} external>
92+
{serviceUrl}
93+
</Link>
94+
</Alert>
95+
</SpaceBetween>
96+
),
97+
isOptional: true,
98+
},
99+
]}
100+
/>
44101
)}
45-
</Container>
102+
</ExpandableSection>
46103
);
47104
};

frontend/src/pages/Runs/Details/RunDetails/ConnectToTaskRun/index.tsx

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ import {
55
Alert,
66
Box,
77
Button,
8-
ButtonDropdown,
98
Code,
10-
Container,
119
ExpandableSection,
12-
Header,
10+
Link,
1311
Popover,
1412
SpaceBetween,
1513
StatusIndicator,
@@ -26,24 +24,33 @@ import styles from '../ConnectToRunWithDevEnvConfiguration/styles.module.scss';
2624
const UvInstallCommand = 'uv tool install dstack -U';
2725
const PipInstallCommand = 'pip install dstack -U';
2826

29-
const getPort = (spec: IAppSpec): number => spec.map_to_port ?? spec.port;
27+
const getMappedPort = (spec: IAppSpec): number | undefined => spec.map_to_port;
3028

3129
export const ConnectToTaskRun: FC<{ run: IRun }> = ({ run }) => {
3230
const { t } = useTranslation();
31+
const [isExpandedConnectSection, setIsExpandedConnectSection] = React.useState(true);
3332

3433
const attachCommand = `dstack attach ${run.run_spec.run_name} --logs`;
3534
const appSpecs = run.jobs[0]?.job_spec?.app_specs ?? [];
35+
const mappedAppSpecs = appSpecs.filter((spec) => getMappedPort(spec) != null);
3636

3737
const [activeStepIndex, setActiveStepIndex] = React.useState(0);
38-
const [selectedPort, setSelectedPort] = React.useState(() => getPort(appSpecs[0]));
3938
const [configCliCommand, copyCliCommand] = useConfigProjectCliCommand({ projectName: run.project_name });
4039

41-
const openPort = (port: number) => window.open(`http://127.0.0.1:${port}`, '_blank');
42-
4340
return (
44-
<Container>
45-
<Header variant="h2">Connect</Header>
46-
41+
<ExpandableSection
42+
variant="container"
43+
headerText="Connect"
44+
expanded={isExpandedConnectSection}
45+
onChange={({ detail }) => setIsExpandedConnectSection(detail.expanded)}
46+
headerActions={
47+
<Button
48+
iconName="script"
49+
variant={isExpandedConnectSection ? 'normal' : 'primary'}
50+
onClick={() => setIsExpandedConnectSection((prev) => !prev)}
51+
/>
52+
}
53+
>
4754
{run.status === 'running' && (
4855
<Wizard
4956
i18nStrings={{
@@ -57,15 +64,15 @@ export const ConnectToTaskRun: FC<{ run: IRun }> = ({ run }) => {
5764
}}
5865
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
5966
activeStepIndex={activeStepIndex}
60-
onSubmit={() => openPort(selectedPort)}
61-
submitButtonText={appSpecs.length === 1 ? 'Open port' : `Open port ${selectedPort}`}
67+
onSubmit={() => setIsExpandedConnectSection(false)}
68+
submitButtonText="Done"
6269
allowSkipTo
6370
steps={[
6471
{
6572
title: 'Attach',
73+
description: 'To access this run, first you need to attach to it.',
6674
content: (
6775
<SpaceBetween size="s">
68-
<Box>To access this run, first you need to attach to it.</Box>
6976
<div className={styles.codeWrapper}>
7077
<Code className={styles.code}>{attachCommand}</Code>
7178

@@ -198,47 +205,55 @@ export const ConnectToTaskRun: FC<{ run: IRun }> = ({ run }) => {
198205
),
199206
isOptional: true,
200207
},
201-
{
202-
title: 'Open',
203-
description: 'After the CLI is attached, you can open the forwarded ports.',
204-
content: (
205-
<SpaceBetween size="s">
206-
{appSpecs.length === 1 ? (
207-
<Button
208-
variant="primary"
209-
external={true}
210-
onClick={() => openPort(getPort(appSpecs[0]))}
211-
>
212-
Open port
213-
</Button>
214-
) : (
215-
<ButtonDropdown
216-
variant="primary"
217-
mainAction={{
218-
text: `Open port ${selectedPort}`,
219-
external: true,
220-
onClick: () => openPort(selectedPort),
221-
}}
222-
items={appSpecs.map((spec) => {
223-
const port = getPort(spec);
224-
225-
return {
226-
id: String(port),
227-
text: `Port ${port}`,
228-
external: true,
229-
};
230-
})}
231-
onItemClick={({ detail }) => {
232-
const port = Number(detail.id);
233-
setSelectedPort(port);
234-
openPort(port);
235-
}}
236-
/>
237-
)}
238-
</SpaceBetween>
239-
),
240-
isOptional: true,
241-
},
208+
...(mappedAppSpecs.length > 0
209+
? [
210+
{
211+
title: 'Open',
212+
description: 'After the CLI is attached, use the forwarded localhost URLs.',
213+
content: (
214+
<SpaceBetween size="s">
215+
{mappedAppSpecs.map((spec) => {
216+
const mappedPort = getMappedPort(spec)!;
217+
const localUrl = `http://127.0.0.1:${mappedPort}`;
218+
219+
return (
220+
<Alert
221+
key={`${spec.port}-${mappedPort}`}
222+
type="info"
223+
action={
224+
<Popover
225+
dismissButton={false}
226+
position="top"
227+
size="small"
228+
triggerType="custom"
229+
content={
230+
<StatusIndicator type="success">
231+
{t('common.copied')}
232+
</StatusIndicator>
233+
}
234+
>
235+
<Button
236+
formAction="none"
237+
iconName="copy"
238+
variant="normal"
239+
onClick={() => copyToClipboard(localUrl)}
240+
/>
241+
</Popover>
242+
}
243+
>
244+
Port {spec.port} is forwarded to{' '}
245+
<Link href={localUrl} external>
246+
{localUrl}
247+
</Link>
248+
</Alert>
249+
);
250+
})}
251+
</SpaceBetween>
252+
),
253+
isOptional: true,
254+
},
255+
]
256+
: []),
242257
]}
243258
/>
244259
)}
@@ -249,6 +264,6 @@ export const ConnectToTaskRun: FC<{ run: IRun }> = ({ run }) => {
249264
<Alert type="info">Waiting for the run to start.</Alert>
250265
</SpaceBetween>
251266
)}
252-
</Container>
267+
</ExpandableSection>
253268
);
254269
};

0 commit comments

Comments
 (0)