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
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
7 changes: 7 additions & 0 deletions frontend/src/layouts/AppLayout/TutorialPanel/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const overlayI18nStrings: AnnotationContextProps.I18nStrings = {
export enum HotspotIds {
ADD_TOP_UP_BALANCE = 'billing-top-up-balance',
PAYMENT_CONTINUE_BUTTON = 'billing-payment-continue-button',
INSTALL_CLI_COMMAND = 'install-cli-command',
CONFIGURE_CLI_COMMAND = 'configure-cli-command',
CREATE_FIRST_PROJECT = 'create-first-project',
}
Expand Down Expand Up @@ -80,6 +81,7 @@ export const BILLING_TUTORIAL: TutorialPanelProps.Tutorial = {
export const CONFIGURE_CLI_TUTORIAL: TutorialPanelProps.Tutorial = {
completed: false,
title: 'Set up the CLI',
prerequisitesAlert: 'Please, create a project before set up the CLI',
description: (
<>
<Box variant="p" color="text-body-secondary" padding={{ top: 'n' }}>
Expand All @@ -92,6 +94,11 @@ export const CONFIGURE_CLI_TUTORIAL: TutorialPanelProps.Tutorial = {
{
title: 'Configure the CLI',
steps: [
{
title: 'Run the CLI install command',
content: 'Run this command on your local machine to install the CLI.',
hotspotId: HotspotIds.INSTALL_CLI_COMMAND,
},
{
title: 'Run the dstack project add command',
content: 'Run this command on your local machine to configure the dstack CLI.',
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ export const useTutorials = () => {
dispatch(updateTutorialPanelState({ createProjectCompleted: true }));
}, []);

const startConfigCliTutorial = useCallback(() => {}, [billingUrl]);
const startConfigCliTutorial = useCallback(() => {
if (projectData?.length) {
navigate(ROUTES.PROJECT.DETAILS.SETTINGS.FORMAT(projectData[0].project_name));
}
}, [projectData]);

const finishConfigCliTutorial = useCallback(() => {
dispatch(updateTutorialPanelState({ configureCLICompleted: true }));
Expand Down Expand Up @@ -160,6 +164,7 @@ export const useTutorials = () => {
completed: configureCLICompleted,
startCallback: startConfigCliTutorial,
finishCallback: finishConfigCliTutorial,
prerequisitesNeeded: !createProjectCompleted,
},

{
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
208 changes: 171 additions & 37 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,7 @@ export const ProjectSettings: React.FC = () => {
const params = useParams();
const navigate = useNavigate();
const paramProjectName = params.projectName ?? '';
const [openHelpPanel] = useHelpPanel();
const [isExpandedCliSection, setIsExpandedCliSection] = React.useState(false);
const [configCliCommand, copyCliCommand] = useConfigProjectCliCommand({ projectName: paramProjectName });

const { isAvailableDeletingPermission, isProjectManager, isProjectAdmin, isAvailableProjectManaging } =
Expand All @@ -60,6 +62,15 @@ export const ProjectSettings: React.FC = () => {

const { data, isLoading, error } = useGetProjectQuery({ name: paramProjectName });

const { data: runsData } = useGetRunsQuery({
project_name: paramProjectName,
limit: 1,
});

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

useEffect(() => {
if (error && 'status' in error && error.status === 404) {
riseRouterException();
Expand Down Expand Up @@ -167,6 +178,8 @@ export const ProjectSettings: React.FC = () => {
});
};

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

if (isLoadingPage)
return (
<Container>
Expand All @@ -179,42 +192,163 @@ 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={isExpandedCliSection}
onChange={({ detail }) => setIsExpandedCliSection(detail.expanded)}
headerActions={
<Button
iconName="script"
variant={isExpandedCliSection ? 'normal' : 'primary'}
onClick={() => setIsExpandedCliSection((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}
<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={() => setIsExpandedCliSection(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: (
<Hotspot hotspotId={HotspotIds.INSTALL_CLI_COMMAND}>
<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>
</>
),
},
]}
/>
</Popover>
</div>
</Hotspot>
</div>
</SpaceBetween>
</Container>
</Hotspot>
),
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
Loading