Skip to content
Open
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
1 change: 1 addition & 0 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const API = {
// Fleets
FLEETS: (projectName: IProject['project_name']) => `${API.BASE()}/project/${projectName}/fleets/list`,
FLEETS_DETAILS: (projectName: IProject['project_name']) => `${API.BASE()}/project/${projectName}/fleets/get`,
FLEETS_APPLY: (projectName: IProject['project_name']) => `${API.BASE()}/project/${projectName}/fleets/apply`,
FLEETS_DELETE: (projectName: IProject['project_name']) => `${API.BASE()}/project/${projectName}/fleets/delete`,
FLEET_INSTANCES_DELETE: (projectName: IProject['project_name']) =>
`${API.BASE()}/project/${projectName}/fleets/delete_instances`,
Expand Down
20 changes: 16 additions & 4 deletions frontend/src/components/ButtonWithConfirmation/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import Box from '@cloudscape-design/components/box';

import { Button } from '../Button';
Expand All @@ -13,20 +14,31 @@ export const ButtonWithConfirmation: React.FC<IProps> = ({
confirmButtonLabel,
...props
}) => {
const { t } = useTranslation();
const [showDeleteConfirm, setShowConfirmDelete] = useState(false);

const toggleDeleteConfirm = () => {
setShowConfirmDelete((val) => !val);
};

const content = typeof confirmContent === 'string' ? <Box variant="span">{confirmContent}</Box> : confirmContent;

const onConfirm = () => {
if (onClick) onClick();

setShowConfirmDelete(false);
};

const getContent = () => {
if (!confirmContent) {
return <Box variant="span">{t('confirm_dialog.message')}</Box>;
}

if (typeof confirmContent === 'string') {
return <Box variant="span">{confirmContent}</Box>;
}

return confirmContent;
};

return (
<>
<Button {...props} onClick={toggleDeleteConfirm} />
Expand All @@ -36,8 +48,8 @@ export const ButtonWithConfirmation: React.FC<IProps> = ({
onDiscard={toggleDeleteConfirm}
onConfirm={onConfirm}
title={confirmTitle}
content={content}
confirmButtonLabel={confirmButtonLabel}
content={getContent()}
confirmButtonLabel={confirmButtonLabel ?? t('common.delete')}
/>
</>
);
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/components/ConfirmationDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IProps } from './types';

export const ConfirmationDialog: React.FC<IProps> = ({
title: titleProp,
content: contentProp,
content,
visible = false,
onDiscard,
onConfirm,
Expand All @@ -18,9 +18,8 @@ export const ConfirmationDialog: React.FC<IProps> = ({
}) => {
const { t } = useTranslation();
const title = titleProp ?? t('confirm_dialog.title');
const content = contentProp ?? <Box variant="span">{t('confirm_dialog.message')}</Box>;
const cancelButtonLabel = cancelButtonLabelProp ?? t('common.cancel');
const confirmButtonLabel = confirmButtonLabelProp ?? t('common.delete');
const confirmButtonLabel = confirmButtonLabelProp ?? t('common.ok');

return (
<Modal
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/components/ConfirmationDialog/slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { RootState } from 'store';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

import { IProps as ConfirmationDialogProps } from './types';

type ConfirmationDialogPropsWithUuid = ConfirmationDialogProps & { uuid: string };

type ConfirmationDialogsStata = {
dialogs: Array<ConfirmationDialogPropsWithUuid>;
};

const initialState: ConfirmationDialogsStata = {
dialogs: [],
};

export const confirmationSlice = createSlice({
name: 'confirmation',
initialState,

reducers: {
open: (state, action: PayloadAction<ConfirmationDialogPropsWithUuid>) => {
state.dialogs = [...state.dialogs, action.payload];
},
close: (state, action: PayloadAction<ConfirmationDialogPropsWithUuid['uuid']>) => {
state.dialogs = state.dialogs.filter((i) => i.uuid !== action.payload);
},
},
});

export const { open, close } = confirmationSlice.actions;

export const selectConfirmationDialogs = (state: RootState) => state.confirmation.dialogs;

export default confirmationSlice.reducer;
57 changes: 57 additions & 0 deletions frontend/src/components/form/Toogle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { Controller, FieldValues } from 'react-hook-form';
import FormField from '@cloudscape-design/components/form-field';
import ToggleCSD from '@cloudscape-design/components/toggle';

import { FormToggleProps } from './types';

export const FormToggle = <T extends FieldValues>({
name,
control,
rules,
label,
info,
constraintText,
description,
secondaryControl,
stretch,
leftContent,
toggleLabel,
onChange: onChangeProp,
...props
}: FormToggleProps<T>) => {
return (
<Controller
name={name}
control={control}
rules={rules}
render={({ field: { onChange, value, ...fieldRest }, fieldState: { error } }) => {
return (
<FormField
description={description}
label={label}
info={info}
stretch={stretch}
constraintText={constraintText}
secondaryControl={secondaryControl}
errorText={error?.message}
>
{leftContent}

<ToggleCSD
{...fieldRest}
{...props}
checked={value}
onChange={(event) => {
onChange(event.detail.checked);
onChangeProp?.(event);
}}
>
{toggleLabel}
</ToggleCSD>
</FormField>
);
}}
/>
);
};
11 changes: 11 additions & 0 deletions frontend/src/components/form/Toogle/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ReactNode } from 'react';
import { ControllerProps, FieldValues } from 'react-hook-form';
import { FormFieldProps } from '@cloudscape-design/components/form-field';
import { ToggleProps } from '@cloudscape-design/components/toggle';

export type FormToggleProps<T extends FieldValues> = Omit<ToggleProps, 'value' | 'checked' | 'name'> &
Omit<FormFieldProps, 'errorText'> &
Pick<ControllerProps<T>, 'control' | 'name' | 'rules'> & {
leftContent?: ReactNode;
toggleLabel?: string;
};
1 change: 1 addition & 0 deletions frontend/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export { ListEmptyMessage } from './ListEmptyMessage';
export { DetailsHeader } from './DetailsHeader';
export { Loader } from './Loader';
export { FormCheckbox } from './form/Checkbox';
export { FormToggle } from './form/Toogle';
export { FormInput } from './form/Input';
export { FormMultiselect } from './form/Multiselect';
export { FormSelect } from './form/Select';
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as useAppDispatch } from './useAppDispatch';
export { default as useAppSelector } from './useAppSelector';
export { useBreadcrumbs } from './useBreadcrumbs';
export { useNotifications } from './useNotifications';
export { useConfirmationDialog } from './useConfirmationDialog';
export { useHelpPanel } from './useHelpPanel';
export { usePermissionGuard } from './usePermissionGuard';
export { useInfiniteScroll } from './useInfiniteScroll';
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/hooks/useConfirmationDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { close, open } from 'components/ConfirmationDialog/slice';
import { IProps as ConfirmationDialogProps } from 'components/ConfirmationDialog/types';

import { getUid } from '../libs';
import useAppDispatch from './useAppDispatch';

export const useConfirmationDialog = () => {
const dispatch = useAppDispatch();

const onDiscard = (uuid: string) => {
dispatch(close(uuid));
};

const openConfirmationDialog = (props: Omit<ConfirmationDialogProps, 'onDiscard'>) => {
const uuid = getUid();

dispatch(
open({
uuid,
...props,
onDiscard: () => onDiscard(uuid),
}),
);
};

return [openConfirmationDialog];
};
1 change: 1 addition & 0 deletions frontend/src/hooks/useNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const NOTIFICATION_LIFE_TIME = 6000;
type TUseNotificationsArgs = { temporary?: boolean; liveTime?: number } | undefined;

const defaultArgs: NonNullable<Required<TUseNotificationsArgs>> = { temporary: true, liveTime: NOTIFICATION_LIFE_TIME };

export const useNotifications = (args: TUseNotificationsArgs = defaultArgs) => {
const dispatch = useAppDispatch();
const notificationIdsSet = useRef(new Set<ReturnType<typeof getUid>>());
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/layouts/AppLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AppLayout as GenericAppLayout,
AppLayoutProps as GenericAppLayoutProps,
BreadcrumbGroup,
ConfirmationDialog,
HelpPanel,
Notifications,
SideNavigation,
Expand All @@ -35,6 +36,7 @@ import {
setToolsTab,
} from 'App/slice';

import { selectConfirmationDialogs } from '../../components/ConfirmationDialog/slice';
import { AnnotationContext } from './AnnotationContext';
import { useSideNavigation } from './hooks';
import { TallyComponent } from './Tally';
Expand Down Expand Up @@ -71,6 +73,7 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const helpPanelContent = useAppSelector(selectHelpPanelContent);
const dispatch = useAppDispatch();
const { navLinks, activeHref } = useSideNavigation();
const confirmationDialogs = useAppSelector(selectConfirmationDialogs);

const onFollowHandler: SideNavigationProps['onFollow'] = (event) => {
event.preventDefault();
Expand Down Expand Up @@ -254,6 +257,10 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
/>

<TallyComponent />

{confirmationDialogs.map(({ uuid, ...props }) => (
<ConfirmationDialog key={uuid} {...props} visible />
))}
</AnnotationContext>
);
};
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/locale/en.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"dstack": "Dstack",
"common": {
"ok": "OK",
"loading": "Loading",
"add": "Add",
"yes": "Yes",
Expand Down Expand Up @@ -207,12 +208,17 @@
"backends_description": "The following backends can be configured with your own cloud credentials in the project settings after the project is created.",
"default_fleet": "Create default fleet",
"default_fleet_description": "You can create default fleet for project",
"fleet_name": "Fleet name",
"fleet_name_description": "Only latin characters, dashes, underscores, and digits",
"fleet_name": "Name",
"fleet_name_description": "The name of the fleet, e.g. 'my-fleet'",
"fleet_name_placeholder": "Optional",
"fleet_name_constraint": "If not specified, generated automatically",
"fleet_min_instances": "Min number of instances",
"fleet_min_instances_description": "Only digits",
"fleet_max_instances": "Max number of instances",
"fleet_max_instances_description": "Only digits",
"fleet_max_instances_placeholder": "Optional",
"fleet_idle_duration": "Idle duration",
"fleet_idle_duration_description": "e.g. 0s, 1m, 1h",
"is_public": "Make project public",
"is_public_description": "Public projects can be accessed by any user without being a member",
"backend": "Backend",
Expand Down
13 changes: 0 additions & 13 deletions frontend/src/pages/Project/CreateWizard/constants.ts

This file was deleted.

42 changes: 42 additions & 0 deletions frontend/src/pages/Project/CreateWizard/constants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

export const projectTypeOptions = [
{
label: 'GPU marketplace',
description:
'Find the cheapest GPUs available in our marketplace. Enjoy $5 in free credits, and easily top up your balance with a credit card.',
value: 'gpu_marketplace',
},
{
label: 'Your cloud accounts',
description: 'Connect and manage your cloud accounts. dstack supports all major GPU cloud providers.',
value: 'own_cloud',
},
];

export const FLEET_MIN_INSTANCES_INFO = {
header: <h2>Min number of instances</h2>,
body: (
<>
<p>Some text</p>
</>
),
};

export const FLEET_MAX_INSTANCES_INFO = {
header: <h2>Max number of instances</h2>,
body: (
<>
<p>Some text</p>
</>
),
};

export const FLEET_IDLE_DURATION_INFO = {
header: <h2>Idle duration</h2>,
body: (
<>
<p>Some text</p>
</>
),
};
Loading