diff --git a/src/atoms/models.ts b/src/atoms/models.ts index edb74c1d..a733192d 100644 --- a/src/atoms/models.ts +++ b/src/atoms/models.ts @@ -35,3 +35,5 @@ export const workerListAtom = atom< >([]); export const backendOptionsAtom = atom([]); + +export const resourceOverviewAtom = atom>({}); diff --git a/src/components/seal-table/components/table-cell.tsx b/src/components/seal-table/components/table-cell.tsx index af4db35f..3996af0b 100644 --- a/src/components/seal-table/components/table-cell.tsx +++ b/src/components/seal-table/components/table-cell.tsx @@ -29,7 +29,7 @@ const CellWrapper = styled.div` `; const TableCell: React.FC = (props) => { - const { dataIndex, render, align, editable } = props; + const { dataIndex, render, align, editable, dataField } = props; return ( = (props) => { })} > diff --git a/src/components/seal-table/types.ts b/src/components/seal-table/types.ts index 2c1ed27c..965297de 100644 --- a/src/components/seal-table/types.ts +++ b/src/components/seal-table/types.ts @@ -25,6 +25,7 @@ export interface SealColumnProps { render?: (text: any, record: any) => React.ReactNode; dataIndex: string; key?: string; + dataField?: string; // Added dataField property, aviods conflict with dataIndex, because dataIndex maybe used in sorting width?: number; span: number; align?: 'left' | 'center' | 'right'; @@ -90,7 +91,7 @@ export interface SealTableProps { watchChildren?: boolean; loading?: boolean; loadend?: boolean; - onCell?: (record: any, dataIndex: string) => void; + onCell?: (record: any, extra: any) => void; onTableSort?: (order: TableOrder | Array) => void; onExpand?: (expanded: boolean, record: any, rowKey: any) => void; onExpandAll?: (expanded: boolean) => void; diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 47ab7c58..17ea5768 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -8,6 +8,7 @@ import routeCachekey from '@/config/route-cachekey'; import { DEFAULT_ENTER_PAGE } from '@/config/settings'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useUserSettings from '@/hooks/use-user-settings'; +import useAddResource from '@/pages/dashboard/hooks/use-add-resource'; import { logout } from '@/pages/login/apis'; import { useAccessMarkedRoutes } from '@@/plugin-access'; import { useModel } from '@@/plugin-model'; @@ -48,6 +49,13 @@ const NO_CONTAINER_PAGES = [ 'clusterCreate' ]; +const CHECK_RESOURCE_PATH = [ + '/resources/workers', + '/cluster-management/clusters/list', + '/cluster-management/credentials', + '/cluster-management/clusters/create' +]; + const loginPath = DEFAULT_ENTER_PAGE.login; // Filter out the routes that need to be displayed, where filterFn indicates the levels that should not be shown @@ -104,6 +112,12 @@ export default (props: any) => { const { initialize: initialize } = useOverlayScroller({ defer: false }); + const { + setLoadingStatus, + fetchResourceData, + NoResourceModal, + loadingStatus + } = useAddResource(); const [modal, contextHolder] = Modal.useModal(); const { themeData, setUserSettings, userSettings } = useUserSettings(); const [userInfo] = useAtom(userAtom); @@ -241,6 +255,10 @@ export default (props: any) => { const { location } = history; const { pathname } = location; + if (!CHECK_RESOURCE_PATH.includes(pathname)) { + // fetchResourceData(); + } + initRouteCacheValue(pathname); dropRouteCache(pathname); @@ -347,6 +365,7 @@ export default (props: any) => { )} + {NoResourceModal} {contextHolder} diff --git a/src/pages/cluster-management/hooks/use-cluster-columns.tsx b/src/pages/cluster-management/hooks/use-cluster-columns.tsx index 37c99999..10fa261a 100644 --- a/src/pages/cluster-management/hooks/use-cluster-columns.tsx +++ b/src/pages/cluster-management/hooks/use-cluster-columns.tsx @@ -44,7 +44,7 @@ const useClusterColumns = ( title: intl.formatMessage({ id: 'clusters.table.provider' }), dataIndex: 'provider', sorter: tableSorter(2), - span: 4, + span: 3, render: (value: string) => ( {ProviderLabelMap[value]} @@ -55,17 +55,20 @@ const useClusterColumns = ( title: 'GPUs', dataIndex: 'gpus', span: 2, + sorter: tableSorter(3), render: (value: number) => {value} }, { title: intl.formatMessage({ id: 'clusters.table.deployments' }), dataIndex: 'models', - span: 2, + sorter: tableSorter(4), + span: 3, render: (value: number) => {value} }, { title: intl.formatMessage({ id: 'resources.nodes' }), dataIndex: 'workers', + sorter: tableSorter(5), span: 3, render: (value: number, record: ClusterListItem) => ( @@ -90,7 +93,7 @@ const useClusterColumns = ( title: intl.formatMessage({ id: 'common.table.createTime' }), dataIndex: 'created_at', defaultSortOrder: 'descend', - sorter: tableSorter(5), + sorter: tableSorter(6), span: 4, render: (value: string) => ( diff --git a/src/pages/cluster-management/hooks/use-cluster-list.ts b/src/pages/cluster-management/hooks/use-cluster-list.ts index 5d865164..b337f598 100644 --- a/src/pages/cluster-management/hooks/use-cluster-list.ts +++ b/src/pages/cluster-management/hooks/use-cluster-list.ts @@ -1,8 +1,13 @@ -import { clusterListAtom, workerListAtom } from '@/atoms/models'; +import { + clusterListAtom, + resourceOverviewAtom, + workerListAtom +} from '@/atoms/models'; import { queryClusterList } from '@/pages/cluster-management/apis'; import { queryWorkersList } from '@/pages/resources/apis'; import { useAtom } from 'jotai'; import { useState } from 'react'; +import { queryDashboardData } from '../../dashboard/apis'; /** * Currently fetch the cluster list and worker list for checking resource existence. @@ -32,6 +37,11 @@ export default function useClusterList() { >([]); const [clustersAtom, setClusterListAtom] = useAtom(clusterListAtom); const [workersAtom, setWorkerListAtom] = useAtom(workerListAtom); + const [resourceAtom, setResourceOverview] = useAtom(resourceOverviewAtom); + const [resourceCount, setResourceCount] = useState>({ + cluster_count: 0, + worker_count: 0 + }); const fetchClusterList = async () => { try { @@ -67,7 +77,7 @@ export default function useClusterList() { } }; - const fetchAll = async () => { + const fetchData = async () => { const [clusters, workers] = await Promise.all([ fetchClusterList(), fetchWorkerList() @@ -82,12 +92,33 @@ export default function useClusterList() { }; }; + const fetchResource = async () => { + try { + const res = await queryDashboardData(); + setResourceOverview(res.resource_counts); + setResourceCount(res.resource_counts); + return { + hasClusters: res.resource_counts?.cluster_count > 0, + hasWorkers: res.resource_counts?.worker_count > 0 + }; + } catch (error) { + setResourceOverview({}); + setResourceCount({}); + return { + hasClusters: false, + hasWorkers: false + }; + } + }; + return { clusterList, workerList, clustersAtom, workersAtom, - fetchAll, + resourceAtom, + resourceCount, + fetchResource, fetchWorkerList, fetchClusterList }; diff --git a/src/pages/dashboard/components/system-load.tsx b/src/pages/dashboard/components/system-load.tsx index 9a058301..d2e90e52 100644 --- a/src/pages/dashboard/components/system-load.tsx +++ b/src/pages/dashboard/components/system-load.tsx @@ -2,6 +2,7 @@ import CardWrapper from '@/components/card-wrapper'; import GaugeChart from '@/components/echarts/gauge'; import PageTools from '@/components/page-tools'; import BaseSelect from '@/components/seal-form/base/select'; +import { queryClusterList } from '@/pages/cluster-management/apis'; import { useIntl } from '@umijs/max'; import { Col, Row } from 'antd'; import _ from 'lodash'; @@ -15,8 +16,11 @@ const resourceChartHeight = 400; const SystemLoad = () => { const intl = useIntl(); - const { system_load, fetchData, clusterList } = useContext(DashboardContext); + const { system_load, fetchData } = useContext(DashboardContext); const [systemLoadData, setSystemLoadData] = useState(system_load || {}); + const [clusterList, setClusterList] = useState[]>( + [] + ); const chartData = useMemo(() => { const data = systemLoadData?.current || {}; @@ -49,6 +53,22 @@ const SystemLoad = () => { } }; + useEffect(() => { + const fetchClusters = async () => { + try { + const res = await queryClusterList({ page: -1 }); + const options = res.items.map((cluster: any) => ({ + label: cluster.name, + value: cluster.id + })); + setClusterList(options); + } catch (error) { + setClusterList([]); + } + }; + fetchClusters(); + }, []); + return (
diff --git a/src/pages/dashboard/config/dashboard-context.ts b/src/pages/dashboard/config/dashboard-context.ts index 8942eb20..304f45f9 100644 --- a/src/pages/dashboard/config/dashboard-context.ts +++ b/src/pages/dashboard/config/dashboard-context.ts @@ -4,7 +4,7 @@ import { DashboardProps } from './types'; export const DashboardContext = createContext< DashboardProps & { fetchData: (params?: { [key: string]: any }) => Promise; - clusterList: Global.BaseOption< + clusterList?: Global.BaseOption< number, { provider: string; state: string | number } >[]; diff --git a/src/pages/dashboard/hooks/use-add-resource.tsx b/src/pages/dashboard/hooks/use-add-resource.tsx index 430396ff..51acd7da 100644 --- a/src/pages/dashboard/hooks/use-add-resource.tsx +++ b/src/pages/dashboard/hooks/use-add-resource.tsx @@ -58,8 +58,7 @@ export default function useAddResource() { const navigate = useNavigate(); const [, setClusterSession] = useAtom(clusterSessionAtom); - const { fetchAll, clusterList, workerList, clustersAtom, workersAtom } = - useClusterList(); + const { fetchResource, resourceCount, resourceAtom } = useClusterList(); const [hiddenModal, setHiddenModal] = useState(false); const [loadingStatus, setLoadingStatus] = useState({ @@ -68,12 +67,14 @@ export default function useAddResource() { }); const isNoResource = useMemo(() => { - const noResource = workerList.length === 0 || clusterList.length === 0; + const noResource = + !resourceAtom?.cluster_count || !resourceAtom?.worker_count; return noResource && !loadingStatus.loading && loadingStatus.loadend; - }, [workersAtom.length, clustersAtom.length, loadingStatus]); + }, [resourceAtom, loadingStatus]); const contentInfo = useMemo(() => { - if (clusterList.length === 0) { + console.log('resourceCount=', resourceCount); + if (!resourceCount.cluster_count) { return { title: intl.formatMessage({ id: 'noresult.cluster.title' }), subTitle: intl.formatMessage({ id: 'noresult.resources.cluster' }), @@ -85,14 +86,14 @@ export default function useAddResource() { subTitle: intl.formatMessage({ id: 'noresult.resources.worker' }), btnText: intl.formatMessage({ id: 'noresult.workers.button.add' }) }; - }, [clusterList.length, workerList.length, intl]); + }, [resourceCount, intl]); const open: boolean = useMemo(() => { return isNoResource && !hiddenModal; }, [isNoResource, hiddenModal]); const handleCreate = () => { - if (clusterList.length === 0) { + if (!resourceCount.cluster_count) { setClusterSession({ firstAddWorker: false, firstAddCluster: true @@ -104,7 +105,7 @@ export default function useAddResource() { return; } - if (workerList.length === 0) { + if (!resourceCount.worker_count) { setClusterSession({ firstAddWorker: true, firstAddCluster: false @@ -117,12 +118,20 @@ export default function useAddResource() { setHiddenModal(true); }; - const Modal = ( + const fetchResourceData = async () => { + setLoadingStatus({ loading: true, loadend: false }); + setHiddenModal(false); + await fetchResource(); + setLoadingStatus({ loading: false, loadend: true }); + }; + + const NoResourceModal = ( @@ -149,11 +158,11 @@ export default function useAddResource() { open, contentInfo, loadingStatus, - clusterList, - Modal, + NoResourceModal, handleCreate, setLoadingStatus, handleCancel, - fetchAll + fetchResource, + fetchResourceData }; } diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index cc8c59c8..634dbf0e 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -6,57 +6,36 @@ import { queryDashboardData } from './apis'; import DashboardInner from './components/dahboard-inner'; import DashboardContext from './config/dashboard-context'; import { DashboardProps } from './config/types'; -import useAddResource from './hooks/use-add-resource'; const Dashboard: React.FC = () => { - const { setLoadingStatus, fetchAll, Modal, loadingStatus, clusterList } = - useAddResource(); const [data, setData] = useState({} as DashboardProps); + const [loading, setLoading] = useState(false); const fetchDashboardData = useMemoizedFn(async () => { try { + setLoading(true); const res = await queryDashboardData(); setData(res); } catch (error) { setData({} as DashboardProps); - } - }); - - const initData = useMemoizedFn(async () => { - try { - setLoadingStatus({ - loading: true, - loadend: false - }); - const { hasClusters, hasWorkers } = await fetchAll(); - if (!hasClusters || !hasWorkers) { - return; - } - await fetchDashboardData(); - } catch (error) { - // ignore } finally { - setLoadingStatus({ - loading: false, - loadend: true - }); + setLoading(false); } }); useEffect(() => { - initData(); + fetchDashboardData(); }, []); return ( - + - {Modal} ); }; diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index 81e3155e..fe188c23 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -693,8 +693,8 @@ const InstanceItem: React.FC = ({ void; onTableSort?: (order: TableOrder | Array) => void; onStatusChange: (value?: any) => void; + onDeleteInstanceFromCache?: (instanceId: number) => void; sortOrder: string[]; queryParams: { page: number; @@ -122,6 +123,7 @@ const Models: React.FC = ({ onStart, onTableSort, onStatusChange, + onDeleteInstanceFromCache, sortOrder, deleteIds, dataSource, @@ -231,7 +233,7 @@ const Models: React.FC = ({ const handleOnCell = useMemoizedFn(async (record: any, extra: any) => { try { - await updateModel(getFormattedData(record)); + await updateModel(getFormattedData(record, { replicas: extra.newValue })); message.success(intl.formatMessage({ id: 'common.message.success' })); if (extra.newValue > extra.oldValue) { updateExpandedRowKeys([record.id, ...expandedRowKeys]); @@ -367,41 +369,37 @@ const Models: React.FC = ({ navigate(`/playground/chat?model=${row.name}`); }; - const handleViewLogs = useCallback( - async (row: any) => { - try { - setCurrentInstance({ - url: `${MODEL_INSTANCE_API}/${row.id}/logs`, - status: row.state, - id: row.id, - modelId: row.model_id, - tail: InstanceRealtimeLogStatus.includes(row.state) - ? undefined - : PageSize - 1 - }); - setOpenLogModal(true); - onViewLogs(); - saveScrollHeight(); - } catch (error) { - console.log('error:', error); - } - }, - [onViewLogs] - ); - const handleDeleteInstace = useCallback( - (row: any) => { - modalRef.current?.show({ - content: 'models.instances', - okText: 'common.button.delrecreate', - operation: 'common.delete.single.confirm', - name: row.name, - async onOk() { - await deleteModelInstance(row.id); - } + const handleViewLogs = async (row: any) => { + try { + setCurrentInstance({ + url: `${MODEL_INSTANCE_API}/${row.id}/logs`, + status: row.state, + id: row.id, + modelId: row.model_id, + tail: InstanceRealtimeLogStatus.includes(row.state) + ? undefined + : PageSize - 1 }); - }, - [deleteModelInstance] - ); + setOpenLogModal(true); + onViewLogs(); + saveScrollHeight(); + } catch (error) { + console.log('error:', error); + } + }; + + const handleDeleteInstace = (row: any) => { + modalRef.current?.show({ + content: 'models.instances', + okText: 'common.button.delrecreate', + operation: 'common.delete.single.confirm', + name: row.name, + async onOk() { + await deleteModelInstance(row.id); + onDeleteInstanceFromCache?.(row.id); + } + }); + }; const getModelInstances = useCallback(async (row: any, options?: any) => { try { diff --git a/src/pages/llmodels/hooks/use-models-columns.tsx b/src/pages/llmodels/hooks/use-models-columns.tsx index 9f7bd6a9..eac49e72 100644 --- a/src/pages/llmodels/hooks/use-models-columns.tsx +++ b/src/pages/llmodels/hooks/use-models-columns.tsx @@ -107,7 +107,8 @@ const useModelsColumns = ({ ), dataIndex: 'ready_replicas', key: 'ready_replicas', - align: 'center', + dataField: 'replicas', + align: 'left', sorter: tableSorter(4), span: 4, editable: { diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index b7aefe1a..07b9ec19 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -331,6 +331,13 @@ const Models: React.FC = () => { }); }; + const handleDeleteInstanceFromCache = (id: number) => { + cacheInsDataListRef.current = cacheInsDataListRef.current.filter( + (item) => item.id !== id + ); + setModelInstances(cacheInsDataListRef.current); + }; + useEffect(() => { let timer: any = null; // fetch data first time @@ -436,6 +443,7 @@ const Models: React.FC = () => { onStop={handleSearchBySilent} onStart={handleSearchBySilent} onTableSort={handleOnSortChange} + onDeleteInstanceFromCache={handleDeleteInstanceFromCache} sortOrder={sortOrder} queryParams={queryParams} loading={dataSource.loading}