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={} + // 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')}} + > +
+
+ + }, + { + label: "pip", + id: "pip", + content: <> +
+ pip install dstack -U + +
+ {t('common.copied')}} + > +
+
+ + }]} + /> + ), + 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={} + headerActions={ + + } // 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')}} - > -
- - + + ), }, { - label: "pip", - id: "pip", - content: <> -
- pip install dstack -U - -
- {t('common.copied')}} - > -
- - - }]} + + ), + }, + ]} /> ), 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')} + + } > + /> } // 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={
- - - ), - }, - { - label: 'pip', - id: 'pip', - content: ( - <> -
- pip install dstack -U - -
- - {t('common.copied')} - - } - > -
- - - ), - }, - ]} - /> + + ), + }, + ]} + /> + ), isOptional: true, },