From 3f71f97f43623f650056a587bac582d31d9576e6 Mon Sep 17 00:00:00 2001
From: peterschmidt85
Date: Mon, 13 Oct 2025 07:47:39 -0700
Subject: [PATCH 1/4] [UX] Improved UX of the project settings CLI section
---
.../Project/Details/Settings/constants.tsx | 2 +-
.../pages/Project/Details/Settings/index.tsx | 190 ++++++++++++++----
.../hooks/useConfigProjectCliComand.ts | 2 +-
3 files changed, 152 insertions(+), 42 deletions(-)
diff --git a/frontend/src/pages/Project/Details/Settings/constants.tsx b/frontend/src/pages/Project/Details/Settings/constants.tsx
index 4b3acefce..426dcb44d 100644
--- a/frontend/src/pages/Project/Details/Settings/constants.tsx
+++ b/frontend/src/pages/Project/Details/Settings/constants.tsx
@@ -13,7 +13,7 @@ export const CLI_INFO = {
To learn how to install the CLI, refer to the{' '}
-
+
installation
{' '}
guide.
diff --git a/frontend/src/pages/Project/Details/Settings/index.tsx b/frontend/src/pages/Project/Details/Settings/index.tsx
index 036681885..e588890f0 100644
--- a/frontend/src/pages/Project/Details/Settings/index.tsx
+++ b/frontend/src/pages/Project/Details/Settings/index.tsx
@@ -2,6 +2,7 @@ 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 Wizard from "@cloudscape-design/components/wizard";
import {
Box,
@@ -40,6 +41,9 @@ import { ProjectSecrets } from '../../Secrets';
import { CLI_INFO } from './constants';
import styles from './styles.module.scss';
+import { ExpandableSection, Tabs } from '@cloudscape-design/components';
+import { useGetRunsQuery } from 'services/run';
+import { copyToClipboard } from 'libs';
export const ProjectSettings: React.FC = () => {
const { t } = useTranslation();
@@ -167,6 +171,21 @@ 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 (
@@ -179,53 +198,144 @@ export const ProjectSettings: React.FC = () => {
{data && backendsData && gatewaysData && (
{isProjectMember && (
- openHelpPanel(CLI_INFO)} />}>
- {t('projects.edit.cli')}
-
- }
+ setExpanded(detail.expanded)}
+ headerActions={ setExpanded((prev) => !prev)}> }
+ // headerInfo={ openHelpPanel(CLI_INFO)} />}
>
-
-
- Run the following commands to set up the CLI for this project
-
-
-
-
- {configCliCommand}
-
-
-
{t('common.copied')}}
- >
-
-
-
-
-
-
-
+
+ `Step ${stepNumber}`,
+ collapsedStepsLabel: (stepNumber, stepsCount) =>
+ `Step ${stepNumber} of ${stepsCount}`,
+ skipToButtonLabel: (step, stepNumber) =>
+ `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: openHelpPanel(CLI_INFO)} />,
+ description: "To use dstack, install the CLI on your local machine.",
+ content: (
+
+
+
uv tool install dstack -U
+
+
+
{t('common.copied')}}
+ >
+ copyToClipboard('uv tool install dstack -U')}
+ />
+
+
+
+ >
+ },
+ {
+ label: "pip",
+ id: "pip",
+ content: <>
+
+
pip install dstack -U
+
+
+
{t('common.copied')}}
+ >
+ copyToClipboard('pip install dstack -U')}
+ />
+
+
+
+ >
+ }]}
+ />
+ ),
+ isOptional: true,
+ },
+ {
+ title: "Add project",
+ // info: openHelpPanel(CLI_INFO)} />,
+ description: "To use dstack with this project, run the following command.",
+ content: (
+
+
+ {configCliCommand}
+
+
+
{t('common.copied')}}
+ >
+
+
+
+
+
+ ),
+ isOptional: true,
+ },
+ ]}
+ />
+
)}
diff --git a/frontend/src/pages/Project/hooks/useConfigProjectCliComand.ts b/frontend/src/pages/Project/hooks/useConfigProjectCliComand.ts
index 2aa2eaed2..d6bacb440 100644
--- a/frontend/src/pages/Project/hooks/useConfigProjectCliComand.ts
+++ b/frontend/src/pages/Project/hooks/useConfigProjectCliComand.ts
@@ -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);
From 67bbe2566f2cec099e24a8dc32ae478ff6600e92 Mon Sep 17 00:00:00 2001
From: peterschmidt85
Date: Mon, 13 Oct 2025 08:34:39 -0700
Subject: [PATCH 2/4] [UI] Linter
---
.pre-commit-config.yaml | 2 +-
frontend/package.json | 2 +-
.../pages/Project/Details/Settings/index.tsx | 185 ++++++++++--------
3 files changed, 103 insertions(+), 86 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 22f7dd3cb..27645fa77 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -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
diff --git a/frontend/package.json b/frontend/package.json
index fa5c511ca..615922889 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -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",
diff --git a/frontend/src/pages/Project/Details/Settings/index.tsx b/frontend/src/pages/Project/Details/Settings/index.tsx
index e588890f0..aee78b503 100644
--- a/frontend/src/pages/Project/Details/Settings/index.tsx
+++ b/frontend/src/pages/Project/Details/Settings/index.tsx
@@ -2,7 +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 Wizard from "@cloudscape-design/components/wizard";
+import { ExpandableSection, Tabs } from '@cloudscape-design/components';
+import Wizard from '@cloudscape-design/components/wizard';
import {
Box,
@@ -12,7 +13,6 @@ import {
Container,
Header,
Hotspot,
- InfoLink,
Loader,
Popover,
SelectCSD,
@@ -21,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';
@@ -38,19 +40,14 @@ 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';
-import { ExpandableSection, Tabs } from '@cloudscape-design/components';
-import { useGetRunsQuery } from 'services/run';
-import { copyToClipboard } from 'libs';
export const ProjectSettings: React.FC = () => {
const { t } = useTranslation();
const params = useParams();
const navigate = useNavigate();
const paramProjectName = params.projectName ?? '';
- const [openHelpPanel] = useHelpPanel();
const [configCliCommand, copyCliCommand] = useConfigProjectCliCommand({ projectName: paramProjectName });
const { isAvailableDeletingPermission, isProjectManager, isProjectAdmin, isAvailableProjectManaging } =
@@ -171,11 +168,7 @@ export const ProjectSettings: React.FC = () => {
});
};
- const [
- activeStepIndex,
- setActiveStepIndex
- ] = React.useState(0);
-
+ const [activeStepIndex, setActiveStepIndex] = React.useState(0);
const { data: runsData } = useGetRunsQuery({
limit: 1,
@@ -203,99 +196,119 @@ export const ProjectSettings: React.FC = () => {
headerText="CLI"
expanded={expanded}
onChange={({ detail }) => setExpanded(detail.expanded)}
- headerActions={ setExpanded((prev) => !prev)}> }
+ headerActions={
+ setExpanded((prev) => !prev)}
+ >
+ }
// headerInfo={ openHelpPanel(CLI_INFO)} />}
>
- `Step ${stepNumber}`,
- collapsedStepsLabel: (stepNumber, stepsCount) =>
- `Step ${stepNumber} of ${stepsCount}`,
- skipToButtonLabel: (step, stepNumber) =>
- `Skip to ${step.title}`,
- navigationAriaLabel: "Steps",
+ 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"
+ previousButton: 'Previous',
+ nextButton: 'Next',
+ optional: 'required',
}}
- onNavigate={({ detail }) =>
- setActiveStepIndex(detail.requestedStepIndex)
- }
+ onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
activeStepIndex={activeStepIndex}
onSubmit={() => setExpanded(false)}
submitButtonText="Dismiss"
allowSkipTo={true}
steps={[
{
- title: "Install CLI",
+ title: 'Install CLI',
// info: openHelpPanel(CLI_INFO)} />,
- description: "To use dstack, install the CLI on your local machine.",
+ description: 'To use dstack, install the CLI on your local machine.',
content: (
-
-
uv tool install dstack -U
-
-
-
{t('common.copied')}}
- >
- copyToClipboard('uv tool install dstack -U')}
- />
-
+ label: 'uv',
+ id: 'uv',
+ content: (
+ <>
+
+
+ uv tool install dstack -U
+
+
+
+
+ {t('common.copied')}
+
+ }
+ >
+
+ copyToClipboard('uv tool install dstack -U')
+ }
+ />
+
+
-
- >
+ >
+ ),
},
{
- label: "pip",
- id: "pip",
- content: <>
-
-
pip install dstack -U
-
-
-
{t('common.copied')}}
- >
- copyToClipboard('pip install dstack -U')}
- />
-
+ label: 'pip',
+ id: 'pip',
+ content: (
+ <>
+
+
pip install dstack -U
+
+
+
+ {t('common.copied')}
+
+ }
+ >
+
+ copyToClipboard('pip install dstack -U')
+ }
+ />
+
+
-
- >
- }]}
+ >
+ ),
+ },
+ ]}
/>
),
isOptional: true,
},
{
- title: "Add project",
+ title: 'Add project',
// info:
openHelpPanel(CLI_INFO)} />,
- description: "To use dstack with this project, run the following command.",
+ description: 'To use dstack with this project, run the following command.',
content: (
@@ -307,7 +320,11 @@ export const ProjectSettings: React.FC = () => {
position="top"
size="small"
triggerType="custom"
- content={{t('common.copied')} }
+ content={
+
+ {t('common.copied')}
+
+ }
>
{
backends={backendsData}
{...(isProjectManager(data)
? {
- onClickAddBackend: addBackend,
- editBackend: editBackend,
- deleteBackends: deleteBackend,
- isDisabledDelete: isDeletingBackend,
- }
+ onClickAddBackend: addBackend,
+ editBackend: editBackend,
+ deleteBackends: deleteBackend,
+ isDisabledDelete: isDeletingBackend,
+ }
: {})}
/>
From 0ce40e3b25c47e1cdead4719973f5be46f5cd079 Mon Sep 17 00:00:00 2001
From: Oleg Vavilov
Date: Mon, 13 Oct 2025 23:28:36 +0300
Subject: [PATCH 3/4] [UX] Improved UX of the project settings CLI section
---
frontend/src/pages/Project/Details/Settings/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/pages/Project/Details/Settings/index.tsx b/frontend/src/pages/Project/Details/Settings/index.tsx
index aee78b503..ea63f9a41 100644
--- a/frontend/src/pages/Project/Details/Settings/index.tsx
+++ b/frontend/src/pages/Project/Details/Settings/index.tsx
@@ -201,7 +201,7 @@ export const ProjectSettings: React.FC = () => {
iconName="script"
variant={expanded ? 'normal' : 'primary'}
onClick={() => setExpanded((prev) => !prev)}
- >
+ />
}
// headerInfo={ openHelpPanel(CLI_INFO)} />}
>
From 6e14111de8d7429773a0ad88dea82dc2e5cd4744 Mon Sep 17 00:00:00 2001
From: Oleg Vavilov
Date: Tue, 14 Oct 2025 20:27:33 +0300
Subject: [PATCH 4/4] [UX] Improved UX of the project settings CLI section
---
.../AppLayout/TutorialPanel/constants.tsx | 7 +
.../layouts/AppLayout/TutorialPanel/hooks.ts | 7 +-
.../pages/Project/Details/Settings/index.tsx | 177 +++++++++---------
3 files changed, 105 insertions(+), 86 deletions(-)
diff --git a/frontend/src/layouts/AppLayout/TutorialPanel/constants.tsx b/frontend/src/layouts/AppLayout/TutorialPanel/constants.tsx
index 5292e24d6..7d4bb378b 100644
--- a/frontend/src/layouts/AppLayout/TutorialPanel/constants.tsx
+++ b/frontend/src/layouts/AppLayout/TutorialPanel/constants.tsx
@@ -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',
}
@@ -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: (
<>
@@ -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.',
diff --git a/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts b/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts
index f27575f26..d3a465fcb 100644
--- a/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts
+++ b/frontend/src/layouts/AppLayout/TutorialPanel/hooks.ts
@@ -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 }));
@@ -160,6 +164,7 @@ export const useTutorials = () => {
completed: configureCLICompleted,
startCallback: startConfigCliTutorial,
finishCallback: finishConfigCliTutorial,
+ prerequisitesNeeded: !createProjectCompleted,
},
{
diff --git a/frontend/src/pages/Project/Details/Settings/index.tsx b/frontend/src/pages/Project/Details/Settings/index.tsx
index ea63f9a41..a73a6fef7 100644
--- a/frontend/src/pages/Project/Details/Settings/index.tsx
+++ b/frontend/src/pages/Project/Details/Settings/index.tsx
@@ -48,6 +48,7 @@ export const ProjectSettings: React.FC = () => {
const params = useParams();
const navigate = useNavigate();
const paramProjectName = params.projectName ?? '';
+ const [isExpandedCliSection, setIsExpandedCliSection] = React.useState(false);
const [configCliCommand, copyCliCommand] = useConfigProjectCliCommand({ projectName: paramProjectName });
const { isAvailableDeletingPermission, isProjectManager, isProjectAdmin, isAvailableProjectManaging } =
@@ -61,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();
@@ -170,15 +180,6 @@ 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 (
@@ -194,13 +195,13 @@ export const ProjectSettings: React.FC = () => {
setExpanded(detail.expanded)}
+ expanded={isExpandedCliSection}
+ onChange={({ detail }) => setIsExpandedCliSection(detail.expanded)}
headerActions={
setExpanded((prev) => !prev)}
+ variant={isExpandedCliSection ? 'normal' : 'primary'}
+ onClick={() => setIsExpandedCliSection((prev) => !prev)}
/>
}
// headerInfo={ openHelpPanel(CLI_INFO)} />}
@@ -218,7 +219,7 @@ export const ProjectSettings: React.FC = () => {
}}
onNavigate={({ detail }) => setActiveStepIndex(detail.requestedStepIndex)}
activeStepIndex={activeStepIndex}
- onSubmit={() => setExpanded(false)}
+ onSubmit={() => setIsExpandedCliSection(false)}
submitButtonText="Dismiss"
allowSkipTo={true}
steps={[
@@ -227,81 +228,87 @@ export const ProjectSettings: React.FC = () => {
// info: openHelpPanel(CLI_INFO)} />,
description: 'To use dstack, install the CLI on your local machine.',
content: (
-
-
-
- uv tool install dstack -U
-
-
-
-
- {t('common.copied')}
-
- }
- >
-
- copyToClipboard('uv tool install dstack -U')
+
+
+
+
+ uv tool install dstack -U
+
+
+
+
+ {t('common.copied')}
+
}
- />
-
+ >
+
+ copyToClipboard(
+ 'uv tool install dstack -U',
+ )
+ }
+ />
+
+
-
- >
- ),
- },
- {
- label: 'pip',
- id: 'pip',
- content: (
- <>
-
-
pip install dstack -U
-
-
-
- {t('common.copied')}
-
- }
- >
-
- copyToClipboard('pip install dstack -U')
+ >
+ ),
+ },
+ {
+ label: 'pip',
+ id: 'pip',
+ content: (
+ <>
+
+
+ pip install dstack -U
+
+
+
+
+ {t('common.copied')}
+
}
- />
-
+ >
+
+ copyToClipboard('pip install dstack -U')
+ }
+ />
+
+
-
- >
- ),
- },
- ]}
- />
+ >
+ ),
+ },
+ ]}
+ />
+
),
isOptional: true,
},