Skip to content

Commit 4b4d1f6

Browse files
peterschmidt85Andrey Cheptsov
andauthored
[UI] Unify Connect UX across run configuration types (plus fix) (#3622)
* [UI] Add Connect section for tasks with ports and fix launch wizard issues Add a Connect component for task runs that expose ports, with a two-step wizard (Attach + Open). Single port shows a simple button; multiple ports use a ButtonDropdown to select and open any forwarded port. Also fix two launch wizard issues: - Add `ports` to the supported YAML fields whitelist - Stop setting `docker: true` when selecting a Docker image (unrelated field) - Fix `IJobSpec.app_specs` type from singular to array - Show `-` for empty configuration path on run details Made-with: Cursor * [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 --------- Co-authored-by: Andrey Cheptsov <andrey.cheptsov@github.com>
1 parent 18fd035 commit 4b4d1f6

11 files changed

Lines changed: 373 additions & 51 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
};

0 commit comments

Comments
 (0)