diff --git a/src/atoms/clusters.ts b/src/atoms/clusters.ts index d2e148cf..bc76dfb1 100644 --- a/src/atoms/clusters.ts +++ b/src/atoms/clusters.ts @@ -1,3 +1,4 @@ +import { ClusterListItem } from '@/pages/cluster-management/config/types'; import { atom } from 'jotai'; // models expand keys: create, update , delete, @@ -63,3 +64,5 @@ export const clusterSessionAtom = atom<{ firstAddWorker: boolean; firstAddCluster: boolean; } | null>(null); + +export const clusterDetailAtom = atom(null); diff --git a/src/pages/cluster-management/cluster-detail.tsx b/src/pages/cluster-management/cluster-detail.tsx index 07131235..dddeaa22 100644 --- a/src/pages/cluster-management/cluster-detail.tsx +++ b/src/pages/cluster-management/cluster-detail.tsx @@ -1,7 +1,11 @@ +import { clusterDetailAtom } from '@/atoms/clusters'; import IconFont from '@/components/icon-font'; +import Deployments from '@/pages/llmodels/index'; +import GPUList from '@/pages/resources/components/gpus'; import WorkerList from '@/pages/resources/components/workers'; import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { Tabs } from 'antd'; +import { useAtomValue } from 'jotai'; import { PageContainerInner } from '../_components/page-box'; import PageBreadcrumb from '../_components/page-breadcrumb'; import ClusterBasic from './components/detail/cluster-basic'; @@ -13,6 +17,7 @@ const ClusterDetailModal = () => { const [searchParams] = useSearchParams(); const id = searchParams.get('id'); const clusterName = searchParams.get('name'); + const clusterDetailData = useAtomValue(clusterDetailAtom); const breadcrumbItems = [ { @@ -39,7 +44,7 @@ const ClusterDetailModal = () => { items={[ { key: 'workers', - label: 'Workers', + label: `Workers (${clusterDetailData?.workers ?? 0})`, icon: , children: ( { }, { key: 'deployments', - label: 'Deployments', + label: `Deployments (${clusterDetailData?.models ?? 0})`, icon: , - children:
Deployments Content
+ children: }, { key: 'gpus', - label: 'GPUs', + label: `GPUs (${clusterDetailData?.gpus ?? 0})`, icon: , - children:
GPUs Content
+ children: } ]} /> diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index 905a5da9..1596598f 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -254,7 +254,7 @@ const Clusters: React.FC = () => { const handleOnCell = useMemoizedFn((record: ClusterListItem, dataIndex) => { if (dataIndex === 'name') { navigate( - `/cluster-management/clusters/detail?id=${record.id}&name=${record.name}` + `/cluster-management/clusters/detail?id=${record.id}&name=${record.name}&page=clusters` ); } }); diff --git a/src/pages/cluster-management/components/detail/cluster-system-load.tsx b/src/pages/cluster-management/components/detail/cluster-system-load.tsx index 8029916a..c307e138 100644 --- a/src/pages/cluster-management/components/detail/cluster-system-load.tsx +++ b/src/pages/cluster-management/components/detail/cluster-system-load.tsx @@ -1,5 +1,5 @@ import CardWrapper from '@/components/card-wrapper'; -import { Col, Progress, Row } from 'antd'; +import { Col, Progress, Row, Tag } from 'antd'; import { round } from 'lodash'; import React, { useEffect } from 'react'; import styled from 'styled-components'; @@ -11,9 +11,12 @@ const Container = styled.div` height: 86px; gap: 16px; .title { + display: flex; + align-items: center; + justify-content: space-between; font-size: 14px; font-weight: 500; - color: var(--ant-color-text-secondary); + color: var(--ant-color-text-tertiary); } .value-wrapper { @@ -42,26 +45,30 @@ const ClusterSystemLoad: React.FC<{ clusterId: number }> = ({ clusterId }) => { } }, [clusterId]); - const renderStepsProgress = (percent: number) => { + const renderStepsProgress = ( + percent: number, + tag: { color: string; text: string } + ) => { return ( ( + + {tag?.text || ''} + + )} > ); }; @@ -73,11 +80,18 @@ const ClusterSystemLoad: React.FC<{ clusterId: number }> = ({ clusterId }) => { -
GPU Utilization
+
+ GPU Utilization +
{`${round(systemLoad.current.gpu, 1)}%`} - {renderStepsProgress(round(systemLoad.current.gpu, 1))} +
+
+ {renderStepsProgress(round(systemLoad.current.gpu, 1), { + color: 'magenta', + text: 'GPU' + })}
@@ -86,17 +100,16 @@ const ClusterSystemLoad: React.FC<{ clusterId: number }> = ({ clusterId }) => { -
VRAM Utilization
+
+ VRAM Utilization +
{`${round(systemLoad.current.vram, 1)}%`}
- + {renderStepsProgress(round(systemLoad.current.vram, 1), { + color: 'purple', + text: 'VRAM' + })}
@@ -105,11 +118,18 @@ const ClusterSystemLoad: React.FC<{ clusterId: number }> = ({ clusterId }) => { -
CPU Utilization
+
+ CPU Utilization +
{`${round(systemLoad.current.cpu, 1)}%`} - {renderStepsProgress(round(systemLoad.current.cpu, 1))} +
+
+ {renderStepsProgress(round(systemLoad.current.cpu, 1), { + color: 'cyan', + text: 'CPU' + })}
@@ -118,19 +138,18 @@ const ClusterSystemLoad: React.FC<{ clusterId: number }> = ({ clusterId }) => { -
Memo Utilization
+
+ Memo Utilization +
{`${round(systemLoad.current.ram, 1)}%`}
- + {renderStepsProgress(round(systemLoad.current.ram, 1), { + color: 'green', + text: 'RAM' + })}
diff --git a/src/pages/cluster-management/hooks/use-cluster-columns.tsx b/src/pages/cluster-management/hooks/use-cluster-columns.tsx index bc5a8399..0f11a431 100644 --- a/src/pages/cluster-management/hooks/use-cluster-columns.tsx +++ b/src/pages/cluster-management/hooks/use-cluster-columns.tsx @@ -6,7 +6,7 @@ import StatusTag from '@/components/status-tag'; import { tableSorter } from '@/config/settings'; import { StarFilled } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Tooltip } from 'antd'; +import { Tooltip, Typography } from 'antd'; import dayjs from 'dayjs'; import { useMemo } from 'react'; import { @@ -42,10 +42,9 @@ const useClusterColumns = ( render: (text: string, record: ClusterListItem) => ( <> - {/* onCellClick?.(record, 'name')}> + onCellClick?.(record, 'name')}> {record.name} - */} - {record.name} + {record.is_default && ( ( + render: (value: number, record: ClusterListItem) => ( ) diff --git a/src/pages/cluster-management/services/use-cluster-detail.ts b/src/pages/cluster-management/services/use-cluster-detail.ts index d566b4bf..a6529566 100644 --- a/src/pages/cluster-management/services/use-cluster-detail.ts +++ b/src/pages/cluster-management/services/use-cluster-detail.ts @@ -1,6 +1,8 @@ +import { clusterDetailAtom } from '@/atoms/clusters'; import { createAxiosToken } from '@/hooks/use-chunk-request'; import { useRequest } from 'ahooks'; import { CancelTokenSource } from 'axios'; +import { useAtom } from 'jotai'; import { useEffect, useRef, useState } from 'react'; import { queryClusterDetail, queryClusterItem } from '../apis'; import { ClusterListItem } from '../config/types'; @@ -79,6 +81,7 @@ export const useClusterDetail = () => { const [clusterDetail, setClusterDetail] = useState( {} as ClusterListItem ); + const [, setClusterDetailAtom] = useAtom(clusterDetailAtom); const { run: fetchClusterDetail, @@ -96,9 +99,11 @@ export const useClusterDetail = () => { manual: true, onSuccess: (response) => { setClusterDetail(response); + setClusterDetailAtom(response); }, onError: () => { setClusterDetail({} as ClusterListItem); + setClusterDetailAtom(null); } } ); diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index cf5271e2..f1086a65 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -526,6 +526,8 @@ const InstanceItem: React.FC = ({ is_main: false, vram: calcTotalVram(item.computed_resource_claim?.vram || {}), gpu_index: _.keys(item.computed_resource_claim?.vram) + .map((i: string) => Number(i)) + .sort((a: number, b: number) => a - b) }; }); diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index 99c88e15..04f23999 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -17,7 +17,7 @@ import PageBox from '@/pages/_components/page-box'; import useNoResourceResult from '@/pages/llmodels/hooks/use-no-resource-result'; import { handleBatchRequest } from '@/utils'; import { DownOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons'; -import { useIntl, useNavigate } from '@umijs/max'; +import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Button, Input, Space, message } from 'antd'; import { useAtom } from 'jotai'; @@ -139,6 +139,8 @@ const Models: React.FC = ({ getWorkerList, workerList } = useFormInitialValues(); + const [searchParams] = useSearchParams(); + const page = searchParams.get('page'); const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const [updateFormInitials, setUpdateFormInitials] = useState<{ data: any; @@ -656,18 +658,20 @@ const Models: React.FC = ({ onChange={handleCategoryChange} options={modelCategories.filter((item) => item.value)} > - + {page !== 'clusters' && ( + + )} = ({ } right={ - - - + + + )} { +const Models: React.FC<{ clusterId?: number }> = ({ clusterId }) => { const { sortOrder, handleMultiSortChange } = useTableMultiSort(); const { setChunkRequest, createAxiosToken } = useSetChunkRequest(); const { setChunkRequest: setModelInstanceChunkRequest } = @@ -45,7 +45,7 @@ const Models: React.FC = () => { page: 1, perPage: 10, search: '', - cluster_id: 0, + cluster_id: clusterId || 0, categories: [], state: '', sort_by: '' diff --git a/src/pages/resources/components/gpus.tsx b/src/pages/resources/components/gpus.tsx index 1fdcf6c5..ffaf300c 100644 --- a/src/pages/resources/components/gpus.tsx +++ b/src/pages/resources/components/gpus.tsx @@ -5,7 +5,7 @@ import useTableFetch from '@/hooks/use-table-fetch'; import NoResult from '@/pages/_components/no-result'; import PageBox from '@/pages/_components/page-box'; import { queryClusterList } from '@/pages/cluster-management/apis'; -import { useIntl } from '@umijs/max'; +import { useIntl, useSearchParams } from '@umijs/max'; import { ConfigProvider, Table } from 'antd'; import _ from 'lodash'; import React, { useEffect, useState } from 'react'; @@ -13,7 +13,10 @@ import { GPU_DEVICES_API, queryGpuDevicesList } from '../apis'; import { GPUDeviceItem } from '../config/types'; import useGPUColumns from '../hooks/use-gpu-columns'; -const GPUList: React.FC = () => { +const GPUList: React.FC<{ clusterId?: number; widths: { input: number } }> = ({ + clusterId, + widths +}) => { const { dataSource, queryParams, @@ -27,9 +30,13 @@ const GPUList: React.FC = () => { } = useTableFetch({ fetchAPI: queryGpuDevicesList, polling: true, - API: GPU_DEVICES_API + API: GPU_DEVICES_API, + defaultQueryParams: { + cluster_id: clusterId + } }); - + const [searchParams] = useSearchParams(); + const page = searchParams.get('page'); const intl = useIntl(); const [clusterList, setClusterList] = useState[]>( [] @@ -97,8 +104,8 @@ const GPUList: React.FC = () => { handleInputChange={handleNameChange} handleSelectChange={handleClusterChange} selectOptions={clusterList} - showSelect - widths={{ input: 200 }} + showSelect={page !== 'clusters'} + widths={{ input: widths?.input || 200 }} > >( () => [ { - title: - sourceType === 'resources' - ? intl.formatMessage({ id: 'common.table.name' }) - : intl.formatMessage({ id: 'resources.table.workername' }), + title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', width: 100, sorter: tableSorter(1),