Skip to content

UI improvements #2489

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 8, 2025
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
5 changes: 3 additions & 2 deletions frontend/src/App/Login/EnterpriseLogin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const EnterpriseLogin: React.FC = () => {
const entraEnabled = entraData?.enabled;

const isLoading = isLoadingOkta || isLoadingEntra;
const isShowTokenForm = !oktaEnabled && !entraEnabled;

return (
<UnauthorizedLayout>
Expand All @@ -32,11 +33,11 @@ export const EnterpriseLogin: React.FC = () => {
{t('auth.sign_in_to_dstack_enterprise')}
</Box>

{!isLoading && !oktaEnabled && <LoginByTokenForm />}
{!isLoading && isShowTokenForm && <LoginByTokenForm />}
{!isLoadingOkta && oktaEnabled && <LoginByOkta className={styles.okta} />}
{!isLoadingEntra && entraEnabled && <LoginByEntraID className={styles.entra} />}

{!isLoading && (oktaEnabled || entraEnabled) && (
{!isLoading && !isShowTokenForm && (
<Box color="text-body-secondary">
<NavigateLink href={ROUTES.AUTH.TOKEN}>{t('auth.login_by_token')}</NavigateLink>
</Box>
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/layouts/AppLayout/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const useSideNavigation = () => {

const isPoolDetails = Boolean(useMatch(ROUTES.FLEETS.DETAILS.TEMPLATE));
const billingUrl = ROUTES.USER.BILLING.LIST.FORMAT(userName);
const userProjectsUrl = ROUTES.USER.PROJECTS.FORMAT(userName);

const generalLinks = [
{ type: 'link', text: t('navigation.runs'), href: ROUTES.RUNS.LIST },
Expand Down Expand Up @@ -49,6 +50,11 @@ export const useSideNavigation = () => {
text: t('navigation.billing'),
href: billingUrl,
},
{
type: 'link',
text: t('users.projects'),
href: userProjectsUrl,
},
].filter(Boolean);

const navLinks: SideNavigationProps['items'] = [
Expand Down Expand Up @@ -93,7 +99,8 @@ export const useSideNavigation = () => {

{
type: 'link',
text: `dstack version: ${serverInfoData?.server_version}`,
href: '#version',
text: `dstack version: ${serverInfoData?.server_version ?? 'No version'}`,
},
].filter(Boolean) as SideNavigationProps['items'];

Expand Down
10 changes: 3 additions & 7 deletions frontend/src/layouts/AppLayout/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@

[class^='awsui_navigation'] {
[class^='awsui_list-container'] {
[class*='awsui_list-variant-root--last'] {
[class^='awsui_list-item']:last-child {
a {
pointer-events: none;
color: awsui.$color-text-status-inactive !important;
}
}
a[href='#version'] {
pointer-events: none;
color: awsui.$color-text-status-inactive !important;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
"provider_name": "Provider",
"status": "Status",
"submitted_at": "Submitted",
"finished_at": "Finished",
"metrics": {
"title": "Metrics",
"show_metrics": "Show metrics",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/Models/Details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,12 +433,12 @@ export const ModelDetails: React.FC = () => {
activeTabId={codeTab}
tabs={[
{
label: 'Python',
label: 'python',
id: CodeTab.Python,
content: <Code>{pythonCode}</Code>,
},
{
label: 'Curl',
label: 'curl',
id: CodeTab.Curl,
content: <Code>{curlCode}</Code>,
},
Expand Down
1 change: 0 additions & 1 deletion frontend/src/pages/Project/Backends/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export const BackendsTable: React.FC<IProps> = ({

const { columns } = useColumnsDefinitions({
...(editBackend ? { onEditClick: (backend) => editBackend(backend) } : {}),
...(deleteBackends ? { onDeleteClick: (backend) => deleteBackends([backend]) } : {}),
});

const renderCounter = () => {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/pages/Project/Details/Settings/constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export const CLI_INFO = {
header: <h2>CLI</h2>,
body: (
<>
<p>Some text</p>
</>
),
};
48 changes: 28 additions & 20 deletions frontend/src/pages/Project/Details/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Container,
Header,
Hotspot,
InfoLink,
Loader,
Popover,
SelectCSD,
Expand All @@ -19,7 +20,7 @@ import {
} from 'components';
import { HotspotIds } from 'layouts/AppLayout/TutorialPanel/constants';

import { useBreadcrumbs, useNotifications } from 'hooks';
import { useBreadcrumbs, useHelpPanel, useNotifications } from 'hooks';
import { riseRouterException } from 'libs';
import { ROUTES } from 'routes';
import { useGetProjectQuery, useUpdateProjectMembersMutation } from 'services/project';
Expand All @@ -33,6 +34,7 @@ import { useBackendsTable } from '../../Backends/hooks';
import { BackendsTable } from '../../Backends/Table';
import { GatewaysTable } from '../../Gateways';
import { useGatewaysTable } from '../../Gateways/hooks';
import { CLI_INFO } from './constants';

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

Expand All @@ -41,7 +43,9 @@ 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 } =
useCheckAvailableProjectPermission();

Expand Down Expand Up @@ -132,22 +136,7 @@ export const ProjectSettings: React.FC = () => {
<SpaceBetween size="l">
<Container
header={
<Header
variant="h2"
actions={
<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}>
{t('common.copy')}
</Button>
</Popover>
}
>
<Header variant="h2" info={<InfoLink onFollow={() => openHelpPanel(CLI_INFO)} />}>
{t('projects.edit.cli')}
</Header>
}
Expand All @@ -157,9 +146,28 @@ export const ProjectSettings: React.FC = () => {
Run the following commands to set up the CLI for this project
</Box>

<Hotspot hotspotId={HotspotIds.CONFIGURE_CLI_COMMAND}>
<Code>{configCliCommand}</Code>
</Hotspot>
<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>

Expand Down
14 changes: 14 additions & 0 deletions frontend/src/pages/Project/Details/Settings/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,17 @@
.dangerSectionField {
width: 300px;
}

.codeWrapper {
position: relative;

.code {
padding: 16px 12px;
}

.copy {
position: absolute;
top: 10px;
right: 8px;
}
}
10 changes: 10 additions & 0 deletions frontend/src/pages/Project/Gateways/Table/constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export const GATEWAYS_INFO = {
header: <h2>Gateways</h2>,
body: (
<>
<p>Some text</p>
</>
),
};
7 changes: 5 additions & 2 deletions frontend/src/pages/Project/Gateways/Table/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React from 'react';
import { useTranslation } from 'react-i18next';

import { Button, ButtonWithConfirmation, Header, ListEmptyMessage, SpaceBetween, Table } from 'components';
import { Button, ButtonWithConfirmation, Header, InfoLink, ListEmptyMessage, SpaceBetween, Table } from 'components';

import { useCollection } from 'hooks';
import { useCollection, useHelpPanel } from 'hooks';

import { GATEWAYS_INFO } from './constants';
import { useColumnsDefinitions } from './hooks';

import { IProps } from './types';

export const GatewaysTable: React.FC<IProps> = ({ gateways, addItem, deleteItem, editItem, isDisabledDelete }) => {
const { t } = useTranslation();
const [openHelpPanel] = useHelpPanel();

const renderEmptyMessage = (): React.ReactNode => {
return (
Expand Down Expand Up @@ -60,6 +62,7 @@ export const GatewaysTable: React.FC<IProps> = ({ gateways, addItem, deleteItem,
header={
<Header
counter={renderCounter()}
info={<InfoLink onFollow={() => openHelpPanel(GATEWAYS_INFO)} />}
actions={
<SpaceBetween size="xs" direction="horizontal">
{/* Disallow adding/editing gateways while custom backends are not supported */}
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 = `pip install dstack\n\ndstack config --url ${location.origin} --project ${projectName} --token ${currentUserToken}`;
const cliCommand = `dstack config --url ${location.origin} --project ${projectName} --token ${currentUserToken}`;

const copyCliCommand = () => {
copyToClipboard(cliCommand);
Expand Down
121 changes: 121 additions & 0 deletions frontend/src/pages/Runs/Details/Jobs/Details/JobDetails/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';

import { Box, ColumnLayout, Container, Header, Loader, StatusIndicator } from 'components';

import { getStatusIconType } from 'libs/run';
import { useGetRunQuery } from 'services/run';

import { Logs } from '../../../Logs';
import {
getJobListItemBackend,
getJobListItemInstance,
getJobListItemPrice,
getJobListItemRegion,
getJobListItemResources,
getJobListItemSpot,
getJobStatus,
getJobSubmittedAt,
getJobTerminationReason,
} from '../../List/helpers';

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

const getJobSubmissionId = (job?: IJob): string | undefined => {
if (!job) return;

return job.job_submissions[job.job_submissions.length - 1]?.id;
};

export const JobDetails = () => {
const { t } = useTranslation();
const params = useParams();
const paramProjectName = params.projectName ?? '';
const paramRunId = params.runId ?? '';
const paramJobName = params.jobName ?? '';

const { data: runData, isLoading: isLoadingRun } = useGetRunQuery({
project_name: paramProjectName,
id: paramRunId,
});

const jobData = useMemo<IJob | null>(() => {
if (!runData) return null;

return runData.jobs.find((job) => job.job_spec.job_name === paramJobName) ?? null;
}, [runData]);

if (isLoadingRun)
return (
<Container>
<Loader />
</Container>
);

if (!jobData) return null;

return (
<div className={styles.details}>
<Container header={<Header variant="h2">{t('common.general')}</Header>}>
<ColumnLayout columns={4} variant="text-grid">
<div>
<Box variant="awsui-key-label">{t('projects.run.submitted_at')}</Box>
<div>{getJobSubmittedAt(jobData)}</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.status')}</Box>
<div>
<StatusIndicator type={getStatusIconType(getJobStatus(jobData))}>
{t(`projects.run.statuses.${getJobStatus(jobData)}`)}
</StatusIndicator>
</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.termination_reason')}</Box>
<div>{getJobTerminationReason(jobData)}</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.backend')}</Box>
<div>{getJobListItemBackend(jobData)}</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.region')}</Box>
<div>{getJobListItemRegion(jobData)}</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.instance')}</Box>
<div>{getJobListItemInstance(jobData)}</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.resources')}</Box>
<div>{getJobListItemResources(jobData)}</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.spot')}</Box>
<div>{getJobListItemSpot(jobData)}</div>
</div>

<div>
<Box variant="awsui-key-label">{t('projects.run.price')}</Box>
<div>{getJobListItemPrice(jobData)}</div>
</div>
</ColumnLayout>
</Container>

<Logs
projectName={paramProjectName}
runName={runData?.run_spec?.run_name ?? ''}
jobSubmissionId={getJobSubmissionId(jobData)}
className={styles.logs}
/>
</div>
);
};
Loading