From 0b223d7266f1fb3eab7ef06ac32cca28d6e5b81f Mon Sep 17 00:00:00 2001 From: Jihyun Kang Date: Tue, 15 Oct 2024 16:57:44 +0900 Subject: [PATCH] refactor: draft for dashboard page --- react/src/App.tsx | 9 +- .../src/components/AllocatedResourcesCard.tsx | 169 ++++++++--- react/src/components/BAIStartSimpleCard.tsx | 1 + react/src/components/DoubleTag.tsx | 24 +- .../src/components/MainLayout/WebUISider.tsx | 11 +- react/src/components/ResourceUnit.tsx | 18 +- react/src/hooks/hooksUsingRelay.tsx | 1 + .../hooks/useResourceLimitAndRemaining.tsx | 6 +- react/src/pages/AdminDashboardPage.tsx | 8 +- react/src/pages/DashboardPage.tsx | 278 +++++++++++++++--- react/src/pages/NeoStorageQuotaCard.tsx | 95 ++++++ react/src/pages/NeoStorageStatusCard.tsx | 83 ++++++ react/src/pages/NeoVFolderListPage.tsx | 143 +++++++++ react/src/pages/NeoVfolderList.tsx | 208 +++++++++++++ 14 files changed, 932 insertions(+), 122 deletions(-) create mode 100644 react/src/pages/NeoStorageQuotaCard.tsx create mode 100644 react/src/pages/NeoStorageStatusCard.tsx create mode 100644 react/src/pages/NeoVFolderListPage.tsx create mode 100644 react/src/pages/NeoVfolderList.tsx diff --git a/react/src/App.tsx b/react/src/App.tsx index e12681a1c0..4bd66d7df0 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -11,7 +11,7 @@ import { useSuspendedBackendaiClient } from './hooks'; import { useBAISettingUserState } from './hooks/useBAISetting'; import Page401 from './pages/Page401'; import Page404 from './pages/Page404'; -import VFolderListPage from './pages/VFolderListPage'; +// import VFolderListPage from './pages/VFolderListPage'; import { Skeleton, theme } from 'antd'; import React, { Suspense } from 'react'; import { FC } from 'react'; @@ -64,6 +64,10 @@ const AdminDashboardPage = React.lazy( ); const DashboardPage = React.lazy(() => import('./pages/DashboardPage')); +const NeoVFolderListPage = React.lazy( + () => import('./pages/NeoVFolderListPage'), +); + const RedirectToStart = () => { useSuspendedBackendaiClient(); const pathName = '/start'; @@ -250,7 +254,8 @@ const router = createBrowserRouter([ handle: { labelKey: 'webui.menu.Data&Storage' }, element: ( - + {/* */} + ), }, diff --git a/react/src/components/AllocatedResourcesCard.tsx b/react/src/components/AllocatedResourcesCard.tsx index 853a44a91a..7284a31d50 100644 --- a/react/src/components/AllocatedResourcesCard.tsx +++ b/react/src/components/AllocatedResourcesCard.tsx @@ -1,8 +1,21 @@ -import { useCurrentProjectValue } from '../hooks/useCurrentProject'; +import { humanReadableBinarySize, iSizeToSize } from '../helper'; +import { useSuspendedBackendaiClient } from '../hooks'; +import { useResourceSlotsDetails } from '../hooks/backendai'; +import { useSuspenseTanQuery } from '../hooks/reactQueryAlias'; +import { + useCurrentProjectValue, + useCurrentResourceGroupValue, +} from '../hooks/useCurrentProject'; +import { + ResourceSlots, + ResourceAllocation, + limitParser, +} from '../hooks/useResourceLimitAndRemaining'; +import BAILayoutCard from './BAILayoutCard'; import Flex from './Flex'; import ResourceGroupSelect from './ResourceGroupSelect'; +import ResourceGroupSelectForCurrentProject from './ResourceGroupSelectForCurrentProject'; import ResourceUnit, { ResourceUnitProps } from './ResourceUnit'; -import BAILayoutCard from './BAILayoutCard'; import { QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons'; import { Button, @@ -30,33 +43,101 @@ const AllocatedResourcesCard: React.FC = ({ }) => { const { token } = theme.useToken(); const { t } = useTranslation(); + const currentResourceGroup = useCurrentResourceGroupValue(); // use global state const currentProject = useCurrentProjectValue(); + const baiClient = useSuspendedBackendaiClient(); + const { mergedResourceSlots } = useResourceSlotsDetails(); + + const { data: resourceAllocation, refetch } = useSuspenseTanQuery< + ResourceAllocation | undefined + >({ + queryKey: ['check-presets', currentProject.name, currentResourceGroup], + queryFn: () => { + if (currentResourceGroup) { + return baiClient.resourcePreset + .check({ + group: currentProject.name, + scaling_group: currentResourceGroup, + }) + .catch(() => {}); + } else { + return; + } + }, + // onSuccess: (data) => { + // if (!data) { + // refetch(); + // } + // }, + // suspense: !_.isEmpty(currentResourceGroup), //prevent flicking + }); + + const mergeResources = (remaining: any, using: any) => { + let merged: ResourceSlots = { + cpu: '', + mem: '', + }; + + [remaining, using].forEach((obj) => { + Object.entries(obj).forEach(([key, value]) => { + const numValue = parseFloat(value as string) || 0; + merged[key] = (parseFloat(merged[key] || '0') + numValue).toString(); + }); + }); + + return merged; + }; + + const remaining = + resourceAllocation?.scaling_groups[currentResourceGroup as string] + ?.remaining || {}; + const using = + resourceAllocation?.scaling_groups[currentResourceGroup as string]?.using || + {}; + const mergedResources: ResourceSlots = mergeResources(remaining, using); + + const accelerators = _.omit(mergedResources, ['cpu', 'mem']); + const usingAccelerators: { [key: string]: string } = _.omit(using, [ + 'cpu', + 'mem', + ]); + + console.log(iSizeToSize(limitParser(using?.mem) + '', 'g', 0)); + + const acceleratorData = _.map(accelerators, (value, key) => ({ + name: mergedResourceSlots[key]?.human_readable_name || key.toUpperCase(), + displayUnit: mergedResourceSlots[key]?.display_unit || 'Unit', + value: usingAccelerators[key], + percentage: + (parseInt(usingAccelerators[key]) / parseInt(mergedResources[key])) * 100, + })); - const resourceUnitMockData: Array = [ + const resourceUnitData: Array = [ { name: 'CPU', displayUnit: 'Core', - value: 12, - percentage: (4 / 12) * 100, + value: using?.cpu as string, + percentage: + (parseInt(using?.cpu as string) / parseInt(mergedResources.cpu)) * 100, }, { name: 'RAM', displayUnit: 'GiB', - value: 256, - percentage: (8 / 12) * 100, - }, - { - name: 'FGPU', - displayUnit: 'GiB', - value: 3.5, - percentage: (4 / 12) * 100, - }, - { - name: 'ATOM', - displayUnit: 'Unit', - value: 2, - percentage: (2 / 12) * 100, + value: iSizeToSize(limitParser(using?.mem) + '', 'g', 0)?.number?.toFixed( + 1, + ) as string, + percentage: + (parseInt( + iSizeToSize(limitParser(using?.mem) + '', 'g', 0) + ?.numberFixed as string, + ) / + parseInt( + iSizeToSize(limitParser(mergedResources.mem) + '', 'g', 0) + ?.numberFixed as string, + )) * + 100, }, + ...acceleratorData, ]; return ( = ({ } extra={ <> - + variant="borderless" + showSearch + /> + + setCurrentTabKey(value)} + items={_.map(TAB_ITEMS_MAP, (label, key) => ({ + key, + label: ( + + {label} + vfolder.status === 'ready', + )?.length as number) + : (_.filter( + vfolders, + (vfolder) => vfolder.status === 'delete-pending', + )?.length as number) + } + color={token.colorPrimary} + /> + + ), + children: ( + vfolder.status === 'ready', + ) + : _.filter( + vfolders, + (vfolder) => vfolder.status === 'delete-pending', + ) + } + /> + ), + }))} + /> + + + { + toggleOpenCreateModal(); + }} + afterClose={() => { + updateFetchKey(); + }} + /> + + ); +}; + +export default NeoVFolderListPage; diff --git a/react/src/pages/NeoVfolderList.tsx b/react/src/pages/NeoVfolderList.tsx new file mode 100644 index 0000000000..9569f73394 --- /dev/null +++ b/react/src/pages/NeoVfolderList.tsx @@ -0,0 +1,208 @@ +import Flex from '../components/Flex'; +import VFolderPermissionTag from '../components/VFolderPermissionTag'; +import { localeCompare } from '../helper'; +import { + CheckCircleOutlined, + DeleteOutlined, + EditOutlined, + SearchOutlined, + TeamOutlined, + UserOutlined, +} from '@ant-design/icons'; +import { Button, Input, Radio, Table, Tag, Typography, theme } from 'antd'; +import { createStyles } from 'antd-style'; +import _ from 'lodash'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +const useStyles = createStyles(({ css }) => ({ + radio: css` + .ant-radio-button-wrapper { + min-width: 100px; + text-align: center; + } + `, +})); + +interface NeoVFolderListProps { + vfolders: any; +} + +const NeoVFolderList: React.FC = ({ vfolders }) => { + const { t } = useTranslation(); + const { styles } = useStyles(); + const { token } = theme.useToken(); + + const [currentRadioValue, setCurrentRadioValue] = useState('all'); + const selectedVFolders = _.filter(vfolders, (vfolder) => { + switch (currentRadioValue) { + case 'all': + return true; + case 'general': + return ( + vfolder.usage_mode === currentRadioValue && + !vfolder.name.startsWith('.') + ); + case 'auto_mount': + return vfolder.name.startsWith('.'); + default: + return vfolder.usage_mode === currentRadioValue; + } + }); + + return ( + + + setCurrentRadioValue(e.target.value)} + optionType="button" + buttonStyle="solid" + defaultValue="running" + /> + } + /> + + { + return index + 1; + }, + }, + { + title: 'Name', + dataIndex: 'name', + sorter: (a, b) => { + return localeCompare(a.name, b.name); + }, + render: (value) => ( + + {value} + + ), + }, + { + title: 'Controls', + render: () => { + return ( + +