Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ repos:
hooks:
- id: frontend-pre-commit
name: frontend-pre-commit
entry: bash -c "cd frontend && npm install && npm run precommit"
entry: bash -c "cd frontend && npm install && npm run pre-commit"
language: system
pass_filenames: false
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "jest",
"test:update-snapshots": "jest -u",
"generate-api": "npx @rtk-query/codegen-openapi openapi-config.ts",
"precommit": "lint-staged"
"pre-commit": "lint-staged"
},
"devDependencies": {
"@babel/cli": "^7.25.9",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/Project/Details/Settings/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const CLI_INFO = {
</p>
<p>
To learn how to install the CLI, refer to the{' '}
<a href={'https://dstack.ai/docs/cli/installation'} target="_blank">
<a href={'https://dstack.ai/docs/installation#set-up-the-cli'} target="_blank">
installation
</a>{' '}
guide.
Expand Down
203 changes: 165 additions & 38 deletions frontend/src/pages/Project/Details/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate, useParams } from 'react-router-dom';
import { debounce } from 'lodash';
import { ExpandableSection, Tabs } from '@cloudscape-design/components';
import Wizard from '@cloudscape-design/components/wizard';

import {
Box,
Expand All @@ -11,7 +13,6 @@ import {
Container,
Header,
Hotspot,
InfoLink,
Loader,
Popover,
SelectCSD,
Expand All @@ -20,10 +21,12 @@ import {
} from 'components';
import { HotspotIds } from 'layouts/AppLayout/TutorialPanel/constants';

import { useBreadcrumbs, useHelpPanel, useNotifications } from 'hooks';
import { useBreadcrumbs, useNotifications } from 'hooks';
import { riseRouterException } from 'libs';
import { copyToClipboard } from 'libs';
import { ROUTES } from 'routes';
import { useGetProjectQuery, useUpdateProjectMembersMutation, useUpdateProjectMutation } from 'services/project';
import { useGetRunsQuery } from 'services/run';
import { useGetUserDataQuery } from 'services/user';

import { useCheckAvailableProjectPermission } from 'pages/Project/hooks/useCheckAvailableProjectPermission';
Expand All @@ -37,7 +40,6 @@ import { BackendsTable } from '../../Backends/Table';
import { GatewaysTable } from '../../Gateways';
import { useGatewaysTable } from '../../Gateways/hooks';
import { ProjectSecrets } from '../../Secrets';
import { CLI_INFO } from './constants';

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

Expand All @@ -46,7 +48,6 @@ export const ProjectSettings: React.FC = () => {
const params = useParams();
const navigate = useNavigate();
const paramProjectName = params.projectName ?? '';
const [openHelpPanel] = useHelpPanel();
const [configCliCommand, copyCliCommand] = useConfigProjectCliCommand({ projectName: paramProjectName });

const { isAvailableDeletingPermission, isProjectManager, isProjectAdmin, isAvailableProjectManaging } =
Expand Down Expand Up @@ -167,6 +168,17 @@ export const ProjectSettings: React.FC = () => {
});
};

const [activeStepIndex, setActiveStepIndex] = React.useState(0);

const { data: runsData } = useGetRunsQuery({
limit: 1,
});
const [expanded, setExpanded] = React.useState(false);

useEffect(() => {
setExpanded(!runsData || runsData.length === 0);
}, [runsData]);

if (isLoadingPage)
return (
<Container>
Expand All @@ -179,42 +191,157 @@ export const ProjectSettings: React.FC = () => {
{data && backendsData && gatewaysData && (
<SpaceBetween size="l">
{isProjectMember && (
<Container
header={
<Header variant="h2" info={<InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />}>
{t('projects.edit.cli')}
</Header>
<ExpandableSection
variant="container"
headerText="CLI"
expanded={expanded}
onChange={({ detail }) => setExpanded(detail.expanded)}
headerActions={
<Button
iconName="script"
variant={expanded ? 'normal' : 'primary'}
onClick={() => setExpanded((prev) => !prev)}
/>
}
// headerInfo={<InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />}
>
<SpaceBetween size="s">
<Box variant="p" color="text-body-secondary">
Run the following commands to set up the CLI for this project
</Box>

<div className={styles.codeWrapper}>
<Hotspot hotspotId={HotspotIds.CONFIGURE_CLI_COMMAND}>
<Code className={styles.code}>{configCliCommand}</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={copyCliCommand}
/>
</Popover>
</div>
</Hotspot>
</div>
</SpaceBetween>
</Container>
<Wizard
i18nStrings={{
stepNumberLabel: (stepNumber) => `Step ${stepNumber}`,
collapsedStepsLabel: (stepNumber, stepsCount) => `Step ${stepNumber} of ${stepsCount}`,
skipToButtonLabel: (step) => `Skip to ${step.title}`,
navigationAriaLabel: 'Steps',
// cancelButton: "Cancel",
previousButton: 'Previous',
nextButton: 'Next',
optional: 'required',
}}
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
activeStepIndex={activeStepIndex}
onSubmit={() => setExpanded(false)}
submitButtonText="Dismiss"
allowSkipTo={true}
steps={[
{
title: 'Install CLI',
// info: <InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />,
description: 'To use dstack, install the CLI on your local machine.',
content: (
<Tabs
variant="stacked"
tabs={[
{
label: 'uv',
id: 'uv',
content: (
<>
<div className={styles.codeWrapper}>
<Code className={styles.code}>
uv tool install dstack -U
</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('uv tool install dstack -U')
}
/>
</Popover>
</div>
</div>
</>
),
},
{
label: 'pip',
id: 'pip',
content: (
<>
<div className={styles.codeWrapper}>
<Code className={styles.code}>pip install dstack -U</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('pip install dstack -U')
}
/>
</Popover>
</div>
</div>
</>
),
},
]}
/>
),
isOptional: true,
},
{
title: 'Add project',
// info: <InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />,
description: 'To use dstack with this project, run the following command.',
content: (
<div className={styles.codeWrapper}>
<Hotspot hotspotId={HotspotIds.CONFIGURE_CLI_COMMAND}>
<Code className={styles.code}>{configCliCommand}</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={copyCliCommand}
/>
</Popover>
</div>
</Hotspot>
</div>
),
isOptional: true,
},
]}
/>
</ExpandableSection>
)}

<BackendsTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Args = {
export const useConfigProjectCliCommand = ({ projectName }: Args) => {
const currentUserToken = useAppSelector(selectAuthToken);

const cliCommand = `dstack project add --name ${projectName} --url ${location.origin} --token ${currentUserToken}`;
const cliCommand = `dstack project add \\\n --url ${location.origin} \\\n --name ${projectName} \\\n --token ${currentUserToken}`;

const copyCliCommand = () => {
copyToClipboard(cliCommand);
Expand Down