From f23da9d251993c05b047af098f3589dafe6d6a59 Mon Sep 17 00:00:00 2001 From: yomybaby <621215+yomybaby@users.noreply.github.com> Date: Wed, 2 Oct 2024 03:45:57 +0000 Subject: [PATCH 1/8] fix: value handling of DynamicUnitInputNumberWithSlider (#2730) **Changes:** This PR fixes and enhances the `DynamicUnitInputNumberWithSlider` component by improving its value handling: 1. Update Input behavior (fix bug and improve UX): - set the controllable value always. 1. Updated slider behavior: - Set value to 0 and disable slider and hide tooltip when min value exceeds max value - Added `filterOutInvalidMarks` function to remove marks that fall outside the valid range(min~ max). 1. Removed dependency on external `isMinOversMaxValue` function from `ResourceAllocationFormItems` Addtionally this PR adds new stories: - "Greater min than max" to demonstrate behavior when min value exceeds max value - "Extra marks" to show custom marks on the slider - "Extra marks with greater min than max" combining both scenarios **Rationale:** These changes improve the component's robustness and flexibility, handling edge cases and providing better visual feedback to users. **Effects:** - Users will see more consistent behavior when min/max values are set incorrectly - Developers can now add custom marks to the slider and test various scenarios using the new stories --- ...namicUnitInputNumberWithSlider.stories.tsx | 43 +++++++++++ .../DynamicUnitInputNumberWithSlider.tsx | 76 +++++++++---------- 2 files changed, 81 insertions(+), 38 deletions(-) diff --git a/react/src/components/DynamicUnitInputNumberWithSlider.stories.tsx b/react/src/components/DynamicUnitInputNumberWithSlider.stories.tsx index c698fbd04a..e251605bb1 100644 --- a/react/src/components/DynamicUnitInputNumberWithSlider.stories.tsx +++ b/react/src/components/DynamicUnitInputNumberWithSlider.stories.tsx @@ -71,3 +71,46 @@ export const AllowOlnyGiB: Story = { units: ['m', 'g'], }, }; + +export const GreaterMinThanMax: Story = { + name: 'Greater min than max', + args: { + min: '3g', + max: '1g', + units: ['m', 'g'], + }, +}; + +export const ExtraMarks: Story = { + name: 'Extra marks', + args: { + min: '0g', + max: '1g', + units: ['m', 'g'], + extraMarks: { + 0.5: { + style: { + color: 'red', + }, + label: '0.5g', + }, + }, + }, +}; + +export const ExtraMarksWithGreaterMinThanMax: Story = { + name: 'Extra marks with greater min than max', + args: { + min: '3g', + max: '1g', + units: ['m', 'g'], + extraMarks: { + 0.5: { + style: { + color: 'red', + }, + label: '0.5g', + }, + }, + }, +}; diff --git a/react/src/components/DynamicUnitInputNumberWithSlider.tsx b/react/src/components/DynamicUnitInputNumberWithSlider.tsx index 38d1cd7e88..830a0b1d60 100644 --- a/react/src/components/DynamicUnitInputNumberWithSlider.tsx +++ b/react/src/components/DynamicUnitInputNumberWithSlider.tsx @@ -5,7 +5,6 @@ import DynamicUnitInputNumber, { DynamicUnitInputNumberProps, } from './DynamicUnitInputNumber'; import Flex from './Flex'; -import { isMinOversMaxValue } from './ResourceAllocationFormItems'; import { Slider, theme } from 'antd'; import { SliderMarks } from 'antd/es/slider'; import _ from 'lodash'; @@ -57,6 +56,22 @@ const DynamicUnitInputNumberWithSlider: React.FC< // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const isMinOversMaxValue = + _.isNumber(minGiB?.number) && + _.isNumber(maxGiB?.number) && + minGiB?.number > maxGiB?.number; + + const filterOutInvalidMarks = (marks: SliderMarks) => { + return _.omitBy({ ...marks }, (option, key) => { + const markNumber = parseFloat(key); + return ( + minGiB && + maxGiB && + (minGiB?.number > markNumber || maxGiB?.number < markNumber) + ); + }); + }; + return ( { setValue(nextValue); }} @@ -81,6 +94,7 @@ const DynamicUnitInputNumberWithSlider: React.FC< minWidth: 130, }} roundStep={step} + changeOnBlur={!isMinOversMaxValue} /> { - return value < 1 - ? `${(value * 1024).toFixed(2)} MiB` - : `${value.toFixed(2)} GiB`; - }, + formatter: isMinOversMaxValue + ? null + : (value = 0) => { + return value < 1 + ? `${(value * 1024).toFixed(2)} MiB` + : `${value.toFixed(2)} GiB`; + }, }} onChange={(newNumValue) => { if (minGiB?.number && minGiB.number > newNumValue) { @@ -174,13 +188,7 @@ const DynamicUnitInputNumberWithSlider: React.FC< ); } }} - marks={{ - // 0: { - // style: { - // color: token.colorTextSecondary, - // }, - // label: 0, - // }, + marks={filterOutInvalidMarks({ ...(minGiB && _.isNumber(minGiB?.number) && { [minGiB.number]: { @@ -188,12 +196,8 @@ const DynamicUnitInputNumberWithSlider: React.FC< color: token.colorTextSecondary, }, // if 0, without unit - label: isMinOversMaxValue( - minGiB?.number as number, - maxGiB?.number as number, - ) - ? undefined - : minGiB.number === 0 + label: + minGiB.number === 0 ? minGiB.number : minGiB.number >= 1 ? minGiB.number + 'g' @@ -211,19 +215,15 @@ const DynamicUnitInputNumberWithSlider: React.FC< style: { color: token.colorTextSecondary, }, - label: isMinOversMaxValue( - minGiB?.number as number, - maxGiB?.number as number, - ) - ? undefined - : maxGiB.number === 0 + label: + maxGiB.number === 0 ? maxGiB.number : maxGiB.number >= 1 ? maxGiB.number + 'g' : maxGiB.number * 1024 + 'm', }, }), - }} + })} /> From 03799dffc66ce95b56073170f76659b5eaf2bfe5 Mon Sep 17 00:00:00 2001 From: agatha197 <28584164+agatha197@users.noreply.github.com> Date: Wed, 2 Oct 2024 03:48:25 +0000 Subject: [PATCH 2/8] feat: change redirect path of `VfolderLazyView` to current page (#2731) **Changes:** This PR modifies the `VFolderLazyView` component to use the current location's pathname when navigating to a folder. Specifically: - Imports the `useLocation` hook from `react-router-dom` - Adds `const location = useLocation();` to get the current location - Updates the `onClick` handler of the `Typography.Link` component to use `location.pathname` instead of the hardcoded '/data' path **Rationale:** This change allows the component to maintain the current route context when navigating to a folder, improving navigation consistency across different pages or sections of the application. **Effects:** Users will experience more predictable navigation behavior when interacting with folders, as the component will preserve the current route context instead of always redirecting to '/data'. **How to test:** 1. make a new service with mounting any folder. 2. go to the serving page. 3. click the folder icon of the mounted folder of service. **Checklist for reviewer:** - [ ] Verify that the folder dialog appears while the current path is maintained **Screenshot:** ![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/2HueYSdFvL8pOB5mgrUQ/a8fb98ef-279e-4da7-a718-0dcec8568a5d.png) **Checklist:** - [ ] Mention to the original issue - [ ] Documentation - [ ] Minium required manager version - [ ] Specific setting for review (eg., KB link, endpoint or how to setup) - [ ] Minimum requirements to check during review - [ ] Test case(s) to demonstrate the difference of before/after --- react/src/components/VFolderLazyView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/react/src/components/VFolderLazyView.tsx b/react/src/components/VFolderLazyView.tsx index 0a59fed537..3efe967a83 100644 --- a/react/src/components/VFolderLazyView.tsx +++ b/react/src/components/VFolderLazyView.tsx @@ -6,6 +6,7 @@ import { VFolder } from './VFolderSelect'; import { FolderOutlined } from '@ant-design/icons'; import { Typography } from 'antd'; import React from 'react'; +import { useLocation } from 'react-router-dom'; interface VFolderLazyViewProps { uuid: string; @@ -17,6 +18,7 @@ const VFolderLazyView: React.FC = ({ }) => { const currentProject = useCurrentProjectValue(); const baiRequestWithPromise = useBaiSignedRequestWithPromise(); + const location = useLocation(); const webuiNavigate = useWebUINavigate(); const { data: vFolders } = useSuspenseTanQuery({ @@ -43,7 +45,7 @@ const VFolderLazyView: React.FC = ({ { webuiNavigate({ - pathname: '/data', + pathname: location.pathname, search: `?folder=${vFolder.id}`, }); }} From 55f1003fed4b3fbe0f2a39cd9215c921cacc3fab Mon Sep 17 00:00:00 2001 From: nghiahsgs Date: Fri, 4 Oct 2024 09:19:31 +0700 Subject: [PATCH 3/8] i18n: Improve Vietnamese translation (#2716) --- resources/i18n/vi.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/i18n/vi.json b/resources/i18n/vi.json index f6cf48b56b..f7722919ba 100644 --- a/resources/i18n/vi.json +++ b/resources/i18n/vi.json @@ -135,7 +135,7 @@ "Requirements": "Yêu cầu", "SessionNameOptional": "Tên phiên (tùy chọn)", "Environments": "Môi trường", - "Start": "Khởi đầu", + "Start": "Bắt đầu", "FolderToMount": "Thư mục để gắn kết", "FolderToMountList": "Thư mục để gắn kết", "ResourceAllocation": "Phân bổ tài nguyên", @@ -163,7 +163,7 @@ "DescSingleNode": "

Khi chạy một phiên, nút được quản lý và các nút công nhân được đặt trên một nút vật lý hoặc máy ảo duy nhất.

", "DescMultiNode": "

Khi chạy một phiên, một nút được quản lý và một hoặc nhiều nút công nhân được chia thành nhiều nút vật lý hoặc máy ảo.

", "CPU": "CPU", - "Memory": "Ký ức", + "Memory": "Bộ nhớ", "TitleSession": "Phiên (Backend.AI)", "PleaseWaitInitializing": "Vui lòng đợi trong khi khởi tạo ...", "MustSpecifyVersion": "Bạn phải chỉ định Môi trường và Phiên bản.", @@ -420,7 +420,7 @@ }, "button": { "Cancel": "Huỷ bỏ", - "Okay": "Được chứ", + "Okay": "Đồng ý", "Create": "Tạo nên", "Edit": "Biên tập", "Share": "Chia sẻ", @@ -466,7 +466,7 @@ "Resources": "Tài nguyên", "Connected": "Đã kết nối", "Terminated": "Đã chấm dứt", - "Maintaining": "Duy trì", + "Maintaining": "Đang bảo trì", "Status": "Trạng thái", "DetailedInformation": "Thông tin chi tiết", "Running": "Đang chạy", @@ -477,7 +477,7 @@ "AgentSettingUpdated": "Đã cập nhật cài đặt đại lý.", "NoChanges": "Không thay đổi.", "AgentSetting": "Cài đặt đại lý", - "Schedulable": "Có thể lên lịch", + "Schedulable": "Có thể được lập lịch", "Architecture": "Ngành kiến ​​​​trúc", "Allocation": "Phân bổ", "Utilization": "Sử dụng", From c9176e301f39187ba09db00141c97ca83d355fe1 Mon Sep 17 00:00:00 2001 From: yomybaby <621215+yomybaby@users.noreply.github.com> Date: Fri, 4 Oct 2024 03:39:47 +0000 Subject: [PATCH 4/8] feat: Session owner setting panel in Neo session launcher (#2693) ### TL;DR Added session owner setter functionality and improved resource group selection. image image ### What changed? - Introduced `SessionOwnerSetterCard` and `SessionOwnerSetterPreviewCard` components for setting session owners - Added `BAISelect` component for enhanced select functionality - Updated `ResourceGroupSelect` to use project name instead of ID - Modified `AvailableResourcesCard` to use `ResourceGroupSelectForCurrentProject` - Updated `SessionLauncherPage` to include session owner setter for admin and superadmin roles - Added new words to the spell checker configuration - Add ErrorBoundary with empty fallback to ignore `SessionLauncherValidationTour`'s error ### How to test? 1. Log in as an admin or superadmin user 2. Navigate to the session launcher page 3. Verify that the session owner setter card is visible 4. Test the functionality of setting a session owner: - Enter an owner's email - Select an access key - Choose a group and resource group 5. Confirm that the preview card updates correctly 6. Launch a session and verify that the owner settings are applied ### Why make this change? This change enhances the session management capabilities for administrators, allowing them to launch sessions on behalf of other users. --- .cspell.json | 3 + react/src/BAICard.tsx | 2 +- .../src/components/AvailableResourcesCard.tsx | 6 +- react/src/components/BAISelect.tsx | 36 +++ react/src/components/HiddenFormItem.tsx | 15 + react/src/components/ResourceGroupSelect.tsx | 27 +- .../SessionLauncherErrorTourProps.tsx | 2 +- .../src/components/SessionOwnerSetterCard.tsx | 306 ++++++++++++++++++ react/src/pages/SessionLauncherPage.tsx | 59 +++- resources/i18n/de.json | 2 +- resources/i18n/el.json | 2 +- resources/i18n/en.json | 2 +- resources/i18n/es.json | 2 +- resources/i18n/fi.json | 2 +- resources/i18n/fr.json | 2 +- resources/i18n/id.json | 2 +- resources/i18n/it.json | 2 +- resources/i18n/ja.json | 2 +- resources/i18n/ko.json | 2 +- resources/i18n/mn.json | 2 +- resources/i18n/ms.json | 2 +- resources/i18n/pl.json | 2 +- resources/i18n/pt-BR.json | 2 +- resources/i18n/pt.json | 2 +- resources/i18n/ru.json | 2 +- resources/i18n/th.json | 2 +- resources/i18n/tr.json | 2 +- resources/i18n/vi.json | 2 +- resources/i18n/zh-CN.json | 2 +- resources/i18n/zh-TW.json | 2 +- 30 files changed, 445 insertions(+), 53 deletions(-) create mode 100644 react/src/components/BAISelect.tsx create mode 100644 react/src/components/HiddenFormItem.tsx create mode 100644 react/src/components/SessionOwnerSetterCard.tsx diff --git a/.cspell.json b/.cspell.json index 1bc6f54916..a88a017c15 100644 --- a/.cspell.json +++ b/.cspell.json @@ -8,6 +8,7 @@ "cssinjs", "cuda", "FGPU", + "filebrowser", "Frgmt", "Gaudi", "keypair", @@ -16,6 +17,8 @@ "Popconfirm", "preopen", "shmem", + "superadmin", + "vfolder", "vfolders", "Warboy", "webcomponent", diff --git a/react/src/BAICard.tsx b/react/src/BAICard.tsx index e5d56b5d62..fb2b484714 100644 --- a/react/src/BAICard.tsx +++ b/react/src/BAICard.tsx @@ -3,7 +3,7 @@ import { Button, Card, CardProps, theme } from 'antd'; import _ from 'lodash'; import React, { ReactNode } from 'react'; -interface BAICardProps extends CardProps { +export interface BAICardProps extends CardProps { status?: 'success' | 'error' | 'warning' | 'default'; extraButtonTitle?: string | ReactNode; onClickExtraButton?: () => void; diff --git a/react/src/components/AvailableResourcesCard.tsx b/react/src/components/AvailableResourcesCard.tsx index 87b4b04443..69fe66c6c8 100644 --- a/react/src/components/AvailableResourcesCard.tsx +++ b/react/src/components/AvailableResourcesCard.tsx @@ -3,7 +3,7 @@ import { useCurrentProjectValue } from '../hooks/useCurrentProject'; import { useResourceLimitAndRemaining } from '../hooks/useResourceLimitAndRemaining'; import BAIProgressWithLabel from './BAIProgressWithLabel'; import Flex from './Flex'; -import ResourceGroupSelect from './ResourceGroupSelect'; +import ResourceGroupSelectForCurrentProject from './ResourceGroupSelectForCurrentProject'; import { QuestionCircleOutlined, ReloadOutlined } from '@ant-design/icons'; import { Button, Card, Tooltip } from 'antd'; import React, { useDeferredValue, useState } from 'react'; @@ -23,11 +23,9 @@ const AvailableResourcesCard = () => { - setSelectedResourceGroup(v)} loading={selectedResourceGroup !== deferredSelectedResourceGroup} popupMatchSelectWidth={false} diff --git a/react/src/components/BAISelect.tsx b/react/src/components/BAISelect.tsx new file mode 100644 index 0000000000..2db26d4b57 --- /dev/null +++ b/react/src/components/BAISelect.tsx @@ -0,0 +1,36 @@ +import { Select, SelectProps } from 'antd'; +import _ from 'lodash'; +import React, { useLayoutEffect } from 'react'; + +interface BAISelectProps extends SelectProps { + autoSelectOption?: boolean | ((options: SelectProps['options']) => any); +} +/** + * BAISelect component. + * + * @component + * @param {Object} props - The component props. + * @param {boolean | Function} props.autoSelectOption - Determines whether to automatically select an option. + * @param {any} props.value - The current value of the select. + * @param {Array} props.options - The available options for the select. + * @param {Function} props.onChange - The callback function to handle value changes. + * @returns {JSX.Element} The rendered BAISelect component. + */ +const BAISelect: React.FC = ({ + autoSelectOption, + ...selectProps +}) => { + const { value, options, onChange } = selectProps; + useLayoutEffect(() => { + if (autoSelectOption && _.isEmpty(value) && options?.[0]) { + if (_.isBoolean(autoSelectOption)) { + onChange?.(options?.[0].value || options?.[0], options?.[0]); + } else if (_.isFunction(autoSelectOption)) { + onChange?.(autoSelectOption(options), options[0]); + } + } + }, [value, options, onChange, autoSelectOption]); + return + + } + > + + {getFieldValue(['owner', 'project']) ? ( + + ) : ( +