Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';

import {
Alert,
Box,
Button,
Code,
Container,
ExpandableSection,
Header,
Popover,
SpaceBetween,
StatusIndicator,
Tabs,
Wizard,
} from 'components';

import { copyToClipboard } from 'libs';

import styles from './styles.module.scss';

const UvInstallCommand = 'uv tool install dstack -U';
const PipInstallCommand = 'pip install dstack -U';

export const ConnectToRunWithDevEnvConfiguration: FC<{ run: IRun }> = ({ run }) => {
const { t } = useTranslation();

const getAttachCommand = (runData: IRun) => {
const attachCommand = `dstack attach ${runData.run_spec.run_name}`;

const copyAttachCommand = () => {
copyToClipboard(attachCommand);
};

return [attachCommand, copyAttachCommand] as const;
};

const getSSHCommand = (runData: IRun) => {
const sshCommand = `ssh ${runData.run_spec.run_name}`;

const copySSHCommand = () => {
copyToClipboard(sshCommand);
};

return [sshCommand, copySSHCommand] as const;
};

const [activeStepIndex, setActiveStepIndex] = React.useState(0);
const [attachCommand, copyAttachCommand] = getAttachCommand(run);
const [sshCommand, copySSHCommand] = getSSHCommand(run);

const openInIDEUrl = `${run.run_spec.configuration.ide}://vscode-remote/ssh-remote+${run.run_spec.run_name}/${run.run_spec.working_dir || 'workflow'}`;

return (
<Container>
<Header variant="h2">Connect</Header>

{run.status === 'running' && (
<Wizard
i18nStrings={{
stepNumberLabel: (stepNumber) => `Step ${stepNumber}`,
collapsedStepsLabel: (stepNumber, stepsCount) => `Step ${stepNumber} of ${stepsCount}`,
skipToButtonLabel: (step) => `Skip to ${step.title}`,
navigationAriaLabel: 'Steps',
previousButton: 'Previous',
nextButton: 'Next',
optional: 'required',
}}
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
activeStepIndex={activeStepIndex}
onSubmit={() => window.open(openInIDEUrl, '_blank')}
submitButtonText="Open in VS Code"
allowSkipTo
steps={[
{
title: 'Attach',
content: (
<SpaceBetween size="s">
<Box>To access this run, first you need to attach to it.</Box>
<div className={styles.codeWrapper}>
<Code className={styles.code}>{attachCommand}</Code>

<div className={styles.copy}>
<Popover
dismissButton={false}
position="top"
size="small"
triggerType="custom"
content={<StatusIndicator type="success">{t('common.copied')}</StatusIndicator>}
>
<Button
formAction="none"
iconName="copy"
variant="normal"
onClick={copyAttachCommand}
/>
</Popover>
</div>
</div>

<ExpandableSection headerText="No CLI installed?">
<SpaceBetween size="s">
<Box />
<Box>To use dstack, install the CLI on your local machine.</Box>

<Tabs
variant="container"
tabs={[
{
label: 'uv',
id: 'uv',
content: (
<>
<div className={styles.codeWrapper}>
<Code className={styles.code}>{UvInstallCommand}</Code>

<div className={styles.copy}>
<Popover
dismissButton={false}
position="top"
size="small"
triggerType="custom"
content={
<StatusIndicator type="success">
{t('common.copied')}
</StatusIndicator>
}
>
<Button
formAction="none"
iconName="copy"
variant="normal"
onClick={() =>
copyToClipboard(UvInstallCommand)
}
/>
</Popover>
</div>
</div>
</>
),
},
{
label: 'pip',
id: 'pip',
content: (
<>
<div className={styles.codeWrapper}>
<Code className={styles.code}>{PipInstallCommand}</Code>

<div className={styles.copy}>
<Popover
dismissButton={false}
position="top"
size="small"
triggerType="custom"
content={
<StatusIndicator type="success">
{t('common.copied')}
</StatusIndicator>
}
>
<Button
formAction="none"
iconName="copy"
variant="normal"
onClick={() =>
copyToClipboard(PipInstallCommand)
}
/>
</Popover>
</div>
</div>
</>
),
},
]}
/>
</SpaceBetween>
</ExpandableSection>
</SpaceBetween>
),
isOptional: true,
},
{
title: 'Open',
description: 'After the CLI is attached, you can open the dev environment in VS Code.',
content: (
<SpaceBetween size="s">
<Button
variant="primary"
external={true}
onClick={() => window.open(openInIDEUrl, '_blank')}
>
Open in VS Code
</Button>

<ExpandableSection headerText="Need plain SSH?">
<SpaceBetween size="s">
<Box />
<div className={styles.codeWrapper}>
<Code className={styles.code}>{sshCommand}</Code>

<div className={styles.copy}>
<Popover
dismissButton={false}
position="top"
size="small"
triggerType="custom"
content={
<StatusIndicator type="success">
{t('common.copied')}
</StatusIndicator>
}
>
<Button
formAction="none"
iconName="copy"
variant="normal"
onClick={() => copySSHCommand()}
/>
</Popover>
</div>
</div>
</SpaceBetween>
</ExpandableSection>
</SpaceBetween>
),
isOptional: true,
},
]}
/>
)}

{run.status === 'running' && (
<SpaceBetween size="s">
<Box />
<Alert type="info">Waiting for the run to start.</Alert>
</SpaceBetween>
)}
</Container>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.codeWrapper {
position: relative;

.code {
padding: 16px 12px;
}

.copy {
position: absolute;
top: 10px;
right: 8px;
}
}
9 changes: 8 additions & 1 deletion frontend/src/pages/Runs/Details/RunDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { Box, ColumnLayout, Container, Header, Loader, NavigateLink, StatusIndic

import { DATE_TIME_FORMAT } from 'consts';
import { getRunError, getRunPriority, getRunStatusMessage, getStatusIconColor, getStatusIconType } from 'libs/run';
import { ROUTES } from 'routes';
import { useGetRunQuery } from 'services/run';

import { finishedRunStatuses } from 'pages/Runs/constants';
import { runIsStopped } from 'pages/Runs/utils';

import { ROUTES } from '../../../../routes';
import {
getRunListItemBackend,
getRunListItemInstanceId,
Expand All @@ -23,6 +24,7 @@ import {
getRunListItemSpot,
} from '../../List/helpers';
import { JobList } from '../Jobs/List';
import { ConnectToRunWithDevEnvConfiguration } from './ConnectToRunWithDevEnvConfiguration';

export const RunDetails = () => {
const { t } = useTranslation();
Expand All @@ -49,6 +51,7 @@ export const RunDetails = () => {
const status = finishedRunStatuses.includes(runData.status)
? (runData.latest_job_submission?.status ?? runData.status)
: runData.status;

const terminationReason = finishedRunStatuses.includes(runData.status)
? runData.latest_job_submission?.termination_reason
: null;
Expand Down Expand Up @@ -168,6 +171,10 @@ export const RunDetails = () => {
)}
</Container>

{runData.run_spec.configuration.type === 'dev-environment' && !runIsStopped(runData.status) && (
<ConnectToRunWithDevEnvConfiguration run={runData} />
)}

{runData.jobs.length > 1 && (
<JobList
projectName={paramProjectName}
Expand Down