From 43936e21d9a892303b456db273a3053adb7d4ff2 Mon Sep 17 00:00:00 2001 From: jialin Date: Thu, 28 Aug 2025 11:37:55 +0800 Subject: [PATCH] feat: instances on worker --- src/assets/images/gpustack-logo-2.png | Bin 0 -> 14160 bytes src/assets/images/gpustack-logo.png | Bin 14160 -> 27624 bytes src/components/scroller-modal/gs-drawer.tsx | 3 +- src/config/global.d.ts | 5 + src/hooks/use-table-fetch.ts | 7 +- src/layouts/Logo.tsx | 2 +- src/layouts/index.tsx | 5 - src/layouts/rightRender.tsx | 65 +--- src/layouts/sider-menu.tsx | 2 +- src/locales/en-US/resources.ts | 1 + src/locales/ja-JP/resources.ts | 4 +- src/locales/ru-RU/resources.ts | 1 + src/locales/zh-CN/resources.ts | 1 + src/pages/cluster-management/config/index.ts | 2 +- .../llmodels/components/instance-item.tsx | 16 +- src/pages/llmodels/config/index.ts | 80 ---- .../components/worker-detail-content.tsx | 125 +++++++ .../components/worker-detail-modal.tsx | 34 ++ src/pages/resources/components/workers.tsx | 29 +- .../resources/hooks/use-instances-columns.tsx | 344 ++++++++++++++++++ .../resources/hooks/use-worker-columns.tsx | 47 ++- 21 files changed, 589 insertions(+), 184 deletions(-) create mode 100644 src/assets/images/gpustack-logo-2.png create mode 100644 src/pages/resources/components/worker-detail-content.tsx create mode 100644 src/pages/resources/components/worker-detail-modal.tsx create mode 100644 src/pages/resources/hooks/use-instances-columns.tsx diff --git a/src/assets/images/gpustack-logo-2.png b/src/assets/images/gpustack-logo-2.png new file mode 100644 index 0000000000000000000000000000000000000000..df995fc93d46b877931270c3b8b8f304230e33ad GIT binary patch literal 14160 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/gpustack-logo.png b/src/assets/images/gpustack-logo.png index df995fc93d46b877931270c3b8b8f304230e33ad..3783e68dfaae19fd636162792dbcdd5089a72ca4 100644 GIT binary patch literal 27624 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 diff --git a/src/components/scroller-modal/gs-drawer.tsx b/src/components/scroller-modal/gs-drawer.tsx index 2d3c55e8..001f9074 100644 --- a/src/components/scroller-modal/gs-drawer.tsx +++ b/src/components/scroller-modal/gs-drawer.tsx @@ -3,7 +3,7 @@ import { CloseOutlined } from '@ant-design/icons'; import { Button, Drawer, type DrawerProps } from 'antd'; const ScrollerModal = (props: DrawerProps) => { - const { title, closable = true, ...restProps } = props; + const { title, closable = true, maskClosable = false, ...restProps } = props; const { EscHint } = useEscHint({ enabled: !props.keyboard && props.open }); @@ -15,6 +15,7 @@ const ScrollerModal = (props: DrawerProps) => { >; } + interface InitialStateType { + fetchUserInfo: () => Promise; + currentUser?: UserInfo; + } + type SearchParams = Pagination & { search?: string }; type MessageType = 'transition' | 'warning' | 'danger' | 'success' | 'info'; diff --git a/src/hooks/use-table-fetch.ts b/src/hooks/use-table-fetch.ts index 7cbf2f83..7bfda6e3 100644 --- a/src/hooks/use-table-fetch.ts +++ b/src/hooks/use-table-fetch.ts @@ -21,6 +21,7 @@ export default function useTableFetch( contentForDelete?: string; defaultData?: any[]; events?: EventsType[]; + defaultQueryParams?: Record; } & WatchConfig ) { const { @@ -31,7 +32,8 @@ export default function useTableFetch( polling = false, watch, defaultData = [], - events = ['UPDATE', 'DELETE'] + events = ['UPDATE', 'DELETE'], + defaultQueryParams = {} } = options; const pollingRef = useRef(null); const chunkRequedtRef = useRef(null); @@ -60,7 +62,8 @@ export default function useTableFetch( const [queryParams, setQueryParams] = useState({ page: 1, perPage: 10, - search: '' + search: '', + ...defaultQueryParams }); const { setChunkRequest } = useSetChunkRequest(); diff --git a/src/layouts/Logo.tsx b/src/layouts/Logo.tsx index a436dd84..b7f50223 100644 --- a/src/layouts/Logo.tsx +++ b/src/layouts/Logo.tsx @@ -4,7 +4,7 @@ import SmallLogo from '@/assets/images/small-logo-200x200.png'; import React from 'react'; const LogoIcon: React.FC = () => { - return logo; + return logo; }; const SLogoIcon: React.FC = () => { return logo; diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 0c3ef090..3292fed8 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -180,11 +180,6 @@ export default (props: any) => { showShortcuts: () => { return showShortcuts(); }, - setTheme: setTheme, - toggleTheme: () => { - const newTheme = userSettings.theme === 'realDark' ? 'light' : 'realDark'; - setTheme(newTheme); - }, notFound: 404 not found }; diff --git a/src/layouts/rightRender.tsx b/src/layouts/rightRender.tsx index 7271ebdc..ac011a9c 100644 --- a/src/layouts/rightRender.tsx +++ b/src/layouts/rightRender.tsx @@ -2,20 +2,17 @@ import avatarImg from '@/assets/images/avatar.png'; import IconFont from '@/components/icon-font'; import externalLinks from '@/constants/external-links'; -import langConfigMap from '@/locales/lang-config-map'; import { DiscordOutlined, GithubOutlined, HomeOutlined, InfoCircleOutlined, LogoutOutlined, - MoonOutlined, MoreOutlined, ReadOutlined, - SettingOutlined, - SunOutlined + SettingOutlined } from '@ant-design/icons'; -import { getAllLocales, history, setLocale } from '@umijs/max'; +import { getAllLocales, history } from '@umijs/max'; import { Avatar, Menu, Spin } from 'antd'; import _ from 'lodash'; import React from 'react'; @@ -191,64 +188,6 @@ export const getRightRenderContent = (opts: { ] }; - const langMenu = { - selectedKeys: [], - className: collapsed - ? 'user-menu-container user-menu-collapsed' - : 'user-menu-container', - mode: 'vertical', - expandIcon: false, - // inlineCollapsed: collapsed, - triggerSubMenuAction: 'hover', - items: [ - { - key: 'lang', - icon: , - label: ( - - {intl?.formatMessage?.({ id: 'common.settings.language' })} - - ), - children: allLocals.map((key) => ({ - key, - label: ( - - {_.get(langConfigMap, [key, 'label'])} - - ), - onClick: () => { - setLocale(key, false); - } - })) - } - ] - }; - - const themeMenu = { - selectedKeys: [], - className: collapsed - ? 'user-menu-container user-menu-collapsed' - : 'user-menu-container', - mode: 'vertical', - expandIcon: false, - // inlineCollapsed: collapsed, - triggerSubMenuAction: 'click', - items: [ - { - key: 'theme', - icon: isDarkTheme ? : , - label: ( - - {intl?.formatMessage?.({ id: 'common.appearance' })} - - ), - onClick: () => { - opts.runtimeConfig.toggleTheme(); - } - } - ] - }; - const userMenu = { selectedKeys: [], className: collapsed diff --git a/src/layouts/sider-menu.tsx b/src/layouts/sider-menu.tsx index a28f9f59..9439595f 100644 --- a/src/layouts/sider-menu.tsx +++ b/src/layouts/sider-menu.tsx @@ -17,7 +17,7 @@ interface MenuItem { interface SiderMenuProps { menuData: MenuItem[]; collapsed?: boolean; - initialState: Global.InitialState; + initialState: Global.InitialStateType; } const useStyles = createStyles(({ css, token }) => { diff --git a/src/locales/en-US/resources.ts b/src/locales/en-US/resources.ts index 0fb9918c..e238edff 100644 --- a/src/locales/en-US/resources.ts +++ b/src/locales/en-US/resources.ts @@ -69,6 +69,7 @@ export default { 'Waiting for the download to complete...', 'resources.filter.worker': 'Filter by worker', 'resources.filter.source': 'Filter by Source', + 'resources.filter.status': 'Filter by Status', 'resources.modelfiles.delete.tips': 'Also delete the file from disk', 'resources.modelfiles.copy.tips': 'Copy Full Path', 'resources.filter.path': 'Filter by path', diff --git a/src/locales/ja-JP/resources.ts b/src/locales/ja-JP/resources.ts index 276cc085..a587f0d2 100644 --- a/src/locales/ja-JP/resources.ts +++ b/src/locales/ja-JP/resources.ts @@ -70,6 +70,7 @@ export default { 'ダウンロード完了を待っています...', 'resources.filter.worker': 'ワーカーでフィルタ', 'resources.filter.source': 'ソースでフィルタ', + 'resources.filter.status': 'Filter by Status', 'resources.modelfiles.delete.tips': 'ディスクからファイルも削除します', 'resources.modelfiles.copy.tips': 'フルパスをコピー', 'resources.filter.path': 'パスでフィルタ', @@ -105,5 +106,6 @@ export default { // 8. 'resources.register.install.title': 'Install GPUStack on {os}', // 9. 'resources.register.download':'Download and install the installer. Only supported: {versions}.', // 10. 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+', -// 11. 'resource.register.windows.support': 'win 10, win 11' +// 11. 'resource.register.windows.support': 'win 10, win 11', +// 12. 'resources.filter.status': 'Filter by Status', // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/resources.ts b/src/locales/ru-RU/resources.ts index 17495a84..1e1a8109 100644 --- a/src/locales/ru-RU/resources.ts +++ b/src/locales/ru-RU/resources.ts @@ -68,6 +68,7 @@ export default { 'resources.modelfiles.storagePath.holder': 'Ожидание завершения загрузки...', 'resources.filter.worker': 'Фильтровать по узлу', 'resources.filter.source': 'Фильтровать по источнику', + 'resources.filter.status': 'Фильтровать по статусу', 'resources.modelfiles.delete.tips': 'Также удалить файл с диска', 'resources.modelfiles.copy.tips': 'Скопировать полный путь', 'resources.filter.path': 'Фильтрация по пути', diff --git a/src/locales/zh-CN/resources.ts b/src/locales/zh-CN/resources.ts index 6e27cb2d..543f71e0 100644 --- a/src/locales/zh-CN/resources.ts +++ b/src/locales/zh-CN/resources.ts @@ -67,6 +67,7 @@ export default { 'resources.modelfiles.storagePath.holder': '等待下载完成...', 'resources.filter.worker': '按 Worker 筛选', 'resources.filter.source': '按来源筛选', + 'resources.filter.status': '按状态筛选', 'resources.modelfiles.delete.tips': '同时从磁盘删除文件', 'resources.modelfiles.copy.tips': '复制完整路径', 'resources.filter.path': '路径查询', diff --git a/src/pages/cluster-management/config/index.ts b/src/pages/cluster-management/config/index.ts index 4beec8e6..0de8547a 100644 --- a/src/pages/cluster-management/config/index.ts +++ b/src/pages/cluster-management/config/index.ts @@ -73,7 +73,7 @@ export const clusterActionList = [ }, { key: 'details', - label: 'common.button.view', + label: 'common.button.detail', icon: icons.DetailInfo }, { diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index 08acea5f..8719c1ad 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -39,6 +39,14 @@ import { } from '../config/types'; import '../style/instance-item.less'; +interface InstanceItemProps { + instanceData: ModelInstanceListItem; + workerList: WorkerListItem[]; + modelData?: any; + defaultOpenId: string; + handleChildSelect: (val: string, item: ModelInstanceListItem) => void; +} + const fieldList = [ { label: 'CPU', @@ -250,14 +258,6 @@ const InstanceStatusTag = ( ); }; -interface InstanceItemProps { - instanceData: ModelInstanceListItem; - workerList: WorkerListItem[]; - modelData?: any; - defaultOpenId: string; - handleChildSelect: (val: string, item: ModelInstanceListItem) => void; -} - const childActionList = [ { label: 'common.button.viewlog', diff --git a/src/pages/llmodels/config/index.ts b/src/pages/llmodels/config/index.ts index 4f3fc139..90266994 100644 --- a/src/pages/llmodels/config/index.ts +++ b/src/pages/llmodels/config/index.ts @@ -2,86 +2,6 @@ import { StatusMaps } from '@/config'; import { EditOutlined } from '@ant-design/icons'; import _ from 'lodash'; -export const ollamaModelOptions = [ - { - label: 'qwen2.5-coder', - value: 'qwen2.5-coder', - name: 'qwen2.5-coder', - id: 'qwen2.5-coder', - tags: [] - }, - { - label: 'llama3.2', - value: 'llama3.2', - name: 'llama3.2', - id: 'llama3.2', - tags: ['Tools', '1B', '3B'] - }, - { - label: 'nomic-embed-text', - value: 'nomic-embed-text', - name: 'nomic-embed-text', - id: 'nomic-embed-text', - tags: [] - }, - { - label: 'qwen2.5', - value: 'qwen2.5', - name: 'qwen2.5', - tags: ['Tools', '0.5B', '1.5B', '3B', '7B', '14B', '32B', '72B'], - id: 'gemma2' - }, - { - label: 'gemma2', - value: 'gemma2', - name: 'gemma2', - tags: ['2B', '9B', '27B'], - id: 'gemma2' - }, - { - label: 'mistral-nemo', - value: 'mistral-nemo', - name: 'mistral-nemo', - tags: ['12B'], - id: 'mistral-nemo' - }, - { - label: 'mistral-large', - value: 'mistral-large', - name: 'mistral-large', - tags: ['123B'], - id: 'mistral-large' - }, - { - label: 'mistral', - value: 'mistral', - name: 'mistral', - tags: ['7B'], - id: 'mistral' - }, - { - label: 'phi3.5', - value: 'phi3.5', - name: 'phi3.5', - tags: ['3B'], - id: 'phi3.5' - }, - { - label: 'codellama', - value: 'codellama', - name: 'codellama', - tags: ['7B', '13B', '34B', '70B'], - id: 'codellama' - }, - { - label: 'deepseek-coder-v2', - value: 'deepseek-coder-v2', - name: 'deepseek-coder-v2', - tags: ['16B', '236B'], - id: 'deepseek-coder-v2' - } -]; - export const backendTipsList = [ { title: 'vLLM', diff --git a/src/pages/resources/components/worker-detail-content.tsx b/src/pages/resources/components/worker-detail-content.tsx new file mode 100644 index 00000000..4df7d18f --- /dev/null +++ b/src/pages/resources/components/worker-detail-content.tsx @@ -0,0 +1,125 @@ +import { FilterBar } from '@/components/page-tools'; +import useTableFetch from '@/hooks/use-table-fetch'; +import { queryClusterList } from '@/pages/cluster-management/apis'; +import { + MODEL_INSTANCE_API, + queryModelsInstances +} from '@/pages/llmodels/apis'; +import { InstanceStatusMap } from '@/pages/llmodels/config'; +import { ModelInstanceListItem } from '@/pages/llmodels/config/types'; +import { useIntl } from '@umijs/max'; +import { ConfigProvider, Empty, Table } from 'antd'; +import React, { useEffect, useState } from 'react'; +import useInstanceColumns from '../hooks/use-instances-columns'; + +const statusList = Object.entries(InstanceStatusMap).map(([key, value]) => ({ + label: key, + value: value +})); + +const WorkerDetailContent: React.FC<{ worker_id: number | undefined }> = ({ + worker_id +}) => { + const { + dataSource, + queryParams, + handlePageChange, + handleTableChange, + handleSearch, + handleQueryChange, + handleNameChange + } = useTableFetch({ + fetchAPI: queryModelsInstances, + polling: false, + API: MODEL_INSTANCE_API, + defaultQueryParams: { + worker_id + } + }); + + const intl = useIntl(); + const [clusterList, setClusterList] = useState[]>( + [] + ); + + const getClusterList = async () => { + try { + const res = await queryClusterList({ page: 1, perPage: 100 }); + const list = res.items?.map((item) => ({ + label: item.name, + value: item.id + })); + setClusterList(list || []); + } catch (error) { + setClusterList([]); + } + }; + + const renderEmpty = (type?: string) => { + if (type !== 'Table') return; + if ( + !dataSource.loading && + dataSource.loadend && + !dataSource.dataList.length + ) { + return ; + } + return

; + }; + + const handleSelectChange = (value: string) => { + handleQueryChange({ state: value }); + }; + + const columns = useInstanceColumns({ + clusterList + }); + + useEffect(() => { + console.log('columns changed!'); + }, [columns]); + + useEffect(() => { + getClusterList(); + }, []); + + return ( + <> + + +
+
+ + ); +}; + +export default WorkerDetailContent; diff --git a/src/pages/resources/components/worker-detail-modal.tsx b/src/pages/resources/components/worker-detail-modal.tsx new file mode 100644 index 00000000..48818a45 --- /dev/null +++ b/src/pages/resources/components/worker-detail-modal.tsx @@ -0,0 +1,34 @@ +import GSDrawer from '@/components/scroller-modal/gs-drawer'; +import React from 'react'; +import { ListItem } from '../config/types'; +import WorkerDetailContent from './worker-detail-content'; + +interface WorkerDetailModalProps { + open: boolean; + onClose?: () => void; + currentData: ListItem | null; +} + +const WorkerDetailModal: React.FC = ({ + open, + onClose, + currentData +}) => { + const handleOnClose = () => { + onClose?.(); + }; + + return ( + + + + ); +}; + +export default WorkerDetailModal; diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index aeea95a3..0f92fa2d 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -16,6 +16,7 @@ import { import { ListItem } from '../config/types'; import useWorkerColumns from '../hooks/use-worker-columns'; import UpdateLabels from './update-labels'; +import WorkerDetailModal from './worker-detail-modal'; const Workers: React.FC = () => { const { @@ -55,6 +56,13 @@ const Workers: React.FC = () => { list: [], data: {} }); + const [workerDetailStatus, setWorkerDetailStatus] = useState<{ + open: boolean; + currentData: ListItem | null; + }>({ + open: false, + currentData: null + }); const getClusterList = async () => { try { @@ -118,6 +126,13 @@ const Workers: React.FC = () => { }); }; + const handleViewDetail = (record: ListItem) => { + setWorkerDetailStatus({ + open: true, + currentData: record + }); + }; + const handleSelect = useMemoizedFn((val: any, record: ListItem) => { if (val === 'edit') { handleUpdateLabels(record); @@ -126,6 +141,9 @@ const Workers: React.FC = () => { if (val === 'delete') { handleDelete(record); } + if (val === 'details') { + handleViewDetail(record); + } }); const renderEmpty = (type?: string) => { @@ -149,8 +167,8 @@ const Workers: React.FC = () => { const columns = useWorkerColumns({ clusterData, - dataSource, - extraStatus, + loadend: dataSource.loadend, + firstLoad: extraStatus.firstLoad, handleSelect }); @@ -216,6 +234,13 @@ const Workers: React.FC = () => { labels: updateLabelsData.data.labels }} > + + setWorkerDetailStatus({ currentData: null, open: false }) + } + /> ); diff --git a/src/pages/resources/hooks/use-instances-columns.tsx b/src/pages/resources/hooks/use-instances-columns.tsx new file mode 100644 index 00000000..0664b23b --- /dev/null +++ b/src/pages/resources/hooks/use-instances-columns.tsx @@ -0,0 +1,344 @@ +import AutoTooltip from '@/components/auto-tooltip'; +import { TooltipOverlayScroller } from '@/components/overlay-scroller'; +import SimpleTabel, { ColumnProps } from '@/components/simple-table'; +import InfoColumn from '@/components/simple-table/info-column'; +import StatusTag from '@/components/status-tag'; +import ThemeTag from '@/components/tags-wrapper/theme-tag'; +import { + InstanceStatusMap, + InstanceStatusMapValue, + status +} from '@/pages/llmodels/config'; +import { + DistributedServerItem, + ModelInstanceListItem as ListItem, + ModelInstanceListItem +} from '@/pages/llmodels/config/types'; +import { convertFileSize } from '@/utils'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { Button, Tooltip } from 'antd'; +import { ColumnsType } from 'antd/lib/table'; +import _ from 'lodash'; +import { useMemo } from 'react'; +import styled from 'styled-components'; + +const GPUIndexWrapper = styled.span` + display: flex; + flex-direction: column; + gap: 2px; +`; + +const StatusWrapper = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const fieldList = [ + { + label: 'CPU', + key: 'cpuoffload', + locale: false + }, + { + label: 'GPU', + key: 'gpuoffload', + locale: false + } +]; + +const renderGpuIndexs = (gpuIndexes: number[]) => { + return ( + + {_.chunk(gpuIndexes, 8).map((item: number[], index: number) => { + return {item.join(',')}; + })} + + ); +}; + +const distributeCols: ColumnProps[] = [ + { + title: 'Worker', + key: 'worker_name', + style: { + wordBreak: 'break-word' + } + }, + { + title: 'IP', + key: 'worker_ip', + render: ({ row }) => { + return row.port ? `${row.worker_ip}:${row.port}` : row.worker_ip; + } + }, + { + title: 'models.table.gpuindex', + locale: true, + key: 'gpu_index', + render: ({ row }) => { + const list = _.sortBy(row.gpu_index, (item: number) => item); + return row.is_main ? ( + <> + {renderGpuIndexs(list)} + (main) + + ) : ( + renderGpuIndexs(list) + ); + } + }, + { + title: 'models.table.vram.allocated', + locale: true, + key: 'vram', + render: ({ rowIndex, row, dataList }) => { + return convertFileSize(row.vram, 1); + } + } +]; + +const calcTotalVram = (vram: Record) => { + return _.sum(_.values(vram)); +}; + +const renderDistributedServer = ( + severList: any[], + instanceData: ModelInstanceListItem +) => { + // const list = _.map(severList, (item: any) => { + // const data = _.find(workerList, { id: item.worker_id }); + // return { + // worker_name: data?.name, + // worker_ip: data?.ip, + // port: '', + // is_main: false, + // vram: calcTotalVram(item.computed_resource_claim?.vram || {}), + // gpu_index: _.keys(item.computed_resource_claim?.vram) + // }; + // }); + + const mainWorker = [ + { + worker_name: `${instanceData.worker_name}`, + worker_ip: `${instanceData.worker_ip}`, + port: '', + vram: calcTotalVram(instanceData.computed_resource_claim?.vram || {}), + is_main: true, + gpu_index: instanceData.gpu_indexes + } + ]; + + return ( +
+ +
+ ); +}; + +const DistributionInfo: React.FC<{ + instanceData: ModelInstanceListItem; +}> = ({ instanceData }) => { + const intl = useIntl(); + const distributed_servers = instanceData.distributed_servers; + const severList: DistributedServerItem[] = + distributed_servers?.subordinate_workers || []; + + if (!severList.length) { + return null; + } + + return ( + + + + {intl.formatMessage({ + id: 'models.table.acrossworker' + })} + + + ); +}; + +const OffloadInfo: React.FC<{ + instanceData: ListItem; +}> = (props) => { + const intl = useIntl(); + const { instanceData } = props; + const total_layers = instanceData.computed_resource_claim?.total_layers; + const offload_layers = instanceData.computed_resource_claim?.offload_layers; + if (total_layers === offload_layers || !total_layers) { + return null; + } + + const offloadData = { + cpuoffload: `${ + _.subtract( + instanceData.computed_resource_claim?.total_layers, + instanceData.computed_resource_claim?.offload_layers + ) || 0 + } ${intl.formatMessage({ + id: 'models.table.layers' + })}`, + gpuoffload: `${instanceData.computed_resource_claim?.offload_layers} ${intl.formatMessage( + { + id: 'models.table.layers' + } + )}` + }; + return ( + } + > + + + {intl.formatMessage({ + id: 'models.table.cpuoffload' + })} + + + ); +}; + +const InstanceStatusTag: React.FC<{ + instanceData: ListItem; +}> = (props) => { + const intl = useIntl(); + const { instanceData } = props; + if (!instanceData.state) { + return null; + } + return ( + <> + + {intl.formatMessage({ id: 'models.list.more.logs' })} + + ) : null + } + statusValue={{ + status: + instanceData.state === InstanceStatusMap.Downloading && + instanceData.download_progress === 100 + ? status[InstanceStatusMap.Running] + : status[instanceData.state], + text: InstanceStatusMapValue[instanceData.state], + message: + instanceData.state === InstanceStatusMap.Downloading && + instanceData.download_progress === 100 + ? '' + : instanceData.state_message + }} + /> + + ); +}; + +const useInstanceColumns = (props: { + clusterList: Global.BaseOption[]; +}): ColumnsType => { + const { clusterList } = props; + const intl = useIntl(); + + return useMemo(() => { + return [ + { + title: intl.formatMessage({ id: 'common.table.name' }), + dataIndex: 'name', + width: 240, + render: (text: string, record: ListItem) => ( + + {text} + + ) + }, + { + title: 'Worker', + dataIndex: 'worker_name', + render: (text: string, record: ListItem) => ( + {text} + ) + }, + { + title: intl.formatMessage({ id: 'models.title' }), + dataIndex: 'model_name', + render: (text: string, record: ListItem) => ( + {text} + ) + }, + { + title: intl.formatMessage({ id: 'clusters.title' }), + dataIndex: 'cluster_id', + render: (text: number, record: ListItem) => ( + + {clusterList.find((item) => item.value === text)?.label} + + ) + }, + { + title: intl.formatMessage({ id: 'common.table.status' }), + dataIndex: 'state', + render: (text: string, record: ListItem) => ( + + + + + + ) + } + ]; + }, [clusterList]); +}; + +export default useInstanceColumns; diff --git a/src/pages/resources/hooks/use-worker-columns.tsx b/src/pages/resources/hooks/use-worker-columns.tsx index f0728767..e358dba6 100644 --- a/src/pages/resources/hooks/use-worker-columns.tsx +++ b/src/pages/resources/hooks/use-worker-columns.tsx @@ -17,7 +17,7 @@ import { ColumnsType } from 'antd/lib/table'; import _ from 'lodash'; import { useMemo } from 'react'; import styled from 'styled-components'; -import { WorkerStatusMapValue, status } from '../config'; +import { WorkerStatusMap, WorkerStatusMapValue, status } from '../config'; import { Filesystem, GPUDeviceItem, ListItem } from '../config/types'; const LabelsWrapper = styled.div` @@ -28,6 +28,11 @@ const LabelsWrapper = styled.div` const ActionList = [ { label: 'common.button.edit', key: 'edit', icon: }, + { + label: 'common.button.detail', + key: 'details', + icon: + }, { label: 'common.button.logs', locale: false, @@ -43,6 +48,15 @@ const ActionList = [ } ]; +const setActions = (row: ListItem) => { + return ActionList.filter((action) => { + if (action.key === 'details' && row.state === WorkerStatusMap.ready) { + return true; + } + return true; + }); +}; + const fieldList = [ { label: 'resources.table.total', @@ -115,14 +129,14 @@ const VRAMCell = ({ devices, intl, rIndex, - dataSource, - extraStatus + loadend, + firstLoad }: { devices: GPUDeviceItem[]; intl: any; rIndex: number; - dataSource: any; - extraStatus: any; + loadend: boolean; + firstLoad: boolean; }) => ( {_.map( @@ -133,12 +147,7 @@ const VRAMCell = ({ [{item.index}] { const useWorkerColumns = ({ clusterData, - dataSource, - extraStatus, + loadend, + firstLoad, handleSelect }: { clusterData: { list: Global.BaseOption[]; data: Record; }; - dataSource: any; - extraStatus: any; + loadend: boolean; + firstLoad: boolean; handleSelect: (action: string, record: ListItem) => void; }): ColumnsType => { const intl = useIntl(); @@ -290,8 +299,8 @@ const useWorkerColumns = ({ devices={record?.status?.gpu_devices} intl={intl} rIndex={rIndex} - dataSource={dataSource} - extraStatus={extraStatus} + loadend={loadend} + firstLoad={firstLoad} /> ) }, @@ -305,13 +314,13 @@ const useWorkerColumns = ({ key: 'operation', render: (_, record) => ( handleSelect(val, record)} /> ) } ], - [intl, clusterData, dataSource, extraStatus, handleSelect] + [intl, clusterData, loadend, firstLoad, handleSelect] ); };