feat: instances on worker
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 27 KiB |
@@ -3,7 +3,7 @@ import { CloseOutlined } from '@ant-design/icons';
|
|||||||
import { Button, Drawer, type DrawerProps } from 'antd';
|
import { Button, Drawer, type DrawerProps } from 'antd';
|
||||||
|
|
||||||
const ScrollerModal = (props: DrawerProps) => {
|
const ScrollerModal = (props: DrawerProps) => {
|
||||||
const { title, closable = true, ...restProps } = props;
|
const { title, closable = true, maskClosable = false, ...restProps } = props;
|
||||||
const { EscHint } = useEscHint({
|
const { EscHint } = useEscHint({
|
||||||
enabled: !props.keyboard && props.open
|
enabled: !props.keyboard && props.open
|
||||||
});
|
});
|
||||||
@@ -15,6 +15,7 @@ const ScrollerModal = (props: DrawerProps) => {
|
|||||||
<Drawer
|
<Drawer
|
||||||
{...restProps}
|
{...restProps}
|
||||||
closable={false}
|
closable={false}
|
||||||
|
maskClosable={maskClosable}
|
||||||
title={
|
title={
|
||||||
<div className="flex-between flex-center">
|
<div className="flex-between flex-center">
|
||||||
<span
|
<span
|
||||||
|
|||||||
Vendored
+5
@@ -44,6 +44,11 @@ declare namespace Global {
|
|||||||
opts?: Array<BaseOption<string | number>>;
|
opts?: Array<BaseOption<string | number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InitialStateType {
|
||||||
|
fetchUserInfo: () => Promise<UserInfo>;
|
||||||
|
currentUser?: UserInfo;
|
||||||
|
}
|
||||||
|
|
||||||
type SearchParams = Pagination & { search?: string };
|
type SearchParams = Pagination & { search?: string };
|
||||||
|
|
||||||
type MessageType = 'transition' | 'warning' | 'danger' | 'success' | 'info';
|
type MessageType = 'transition' | 'warning' | 'danger' | 'success' | 'info';
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default function useTableFetch<ListItem>(
|
|||||||
contentForDelete?: string;
|
contentForDelete?: string;
|
||||||
defaultData?: any[];
|
defaultData?: any[];
|
||||||
events?: EventsType[];
|
events?: EventsType[];
|
||||||
|
defaultQueryParams?: Record<string, any>;
|
||||||
} & WatchConfig
|
} & WatchConfig
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
@@ -31,7 +32,8 @@ export default function useTableFetch<ListItem>(
|
|||||||
polling = false,
|
polling = false,
|
||||||
watch,
|
watch,
|
||||||
defaultData = [],
|
defaultData = [],
|
||||||
events = ['UPDATE', 'DELETE']
|
events = ['UPDATE', 'DELETE'],
|
||||||
|
defaultQueryParams = {}
|
||||||
} = options;
|
} = options;
|
||||||
const pollingRef = useRef<any>(null);
|
const pollingRef = useRef<any>(null);
|
||||||
const chunkRequedtRef = useRef<any>(null);
|
const chunkRequedtRef = useRef<any>(null);
|
||||||
@@ -60,7 +62,8 @@ export default function useTableFetch<ListItem>(
|
|||||||
const [queryParams, setQueryParams] = useState<any>({
|
const [queryParams, setQueryParams] = useState<any>({
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 10,
|
perPage: 10,
|
||||||
search: ''
|
search: '',
|
||||||
|
...defaultQueryParams
|
||||||
});
|
});
|
||||||
|
|
||||||
const { setChunkRequest } = useSetChunkRequest();
|
const { setChunkRequest } = useSetChunkRequest();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import SmallLogo from '@/assets/images/small-logo-200x200.png';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const LogoIcon: React.FC = () => {
|
const LogoIcon: React.FC = () => {
|
||||||
return <img src={GpustackLogo} alt="logo" style={{ height: 16 }} />;
|
return <img src={GpustackLogo} alt="logo" style={{ height: 24 }} />;
|
||||||
};
|
};
|
||||||
const SLogoIcon: React.FC = () => {
|
const SLogoIcon: React.FC = () => {
|
||||||
return <img src={SmallLogo} alt="logo" style={{ height: 24 }} />;
|
return <img src={SmallLogo} alt="logo" style={{ height: 24 }} />;
|
||||||
|
|||||||
@@ -180,11 +180,6 @@ export default (props: any) => {
|
|||||||
showShortcuts: () => {
|
showShortcuts: () => {
|
||||||
return showShortcuts();
|
return showShortcuts();
|
||||||
},
|
},
|
||||||
setTheme: setTheme,
|
|
||||||
toggleTheme: () => {
|
|
||||||
const newTheme = userSettings.theme === 'realDark' ? 'light' : 'realDark';
|
|
||||||
setTheme(newTheme);
|
|
||||||
},
|
|
||||||
notFound: <span>404 not found</span>
|
notFound: <span>404 not found</span>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,17 @@
|
|||||||
import avatarImg from '@/assets/images/avatar.png';
|
import avatarImg from '@/assets/images/avatar.png';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import externalLinks from '@/constants/external-links';
|
import externalLinks from '@/constants/external-links';
|
||||||
import langConfigMap from '@/locales/lang-config-map';
|
|
||||||
import {
|
import {
|
||||||
DiscordOutlined,
|
DiscordOutlined,
|
||||||
GithubOutlined,
|
GithubOutlined,
|
||||||
HomeOutlined,
|
HomeOutlined,
|
||||||
InfoCircleOutlined,
|
InfoCircleOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
MoonOutlined,
|
|
||||||
MoreOutlined,
|
MoreOutlined,
|
||||||
ReadOutlined,
|
ReadOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined
|
||||||
SunOutlined
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { getAllLocales, history, setLocale } from '@umijs/max';
|
import { getAllLocales, history } from '@umijs/max';
|
||||||
import { Avatar, Menu, Spin } from 'antd';
|
import { Avatar, Menu, Spin } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
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: <IconFont type="icon-language" />,
|
|
||||||
label: (
|
|
||||||
<span className="sub-title">
|
|
||||||
{intl?.formatMessage?.({ id: 'common.settings.language' })}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
children: allLocals.map((key) => ({
|
|
||||||
key,
|
|
||||||
label: (
|
|
||||||
<span className="flex flex-center">
|
|
||||||
<span>{_.get(langConfigMap, [key, 'label'])}</span>
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
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 ? <MoonOutlined /> : <SunOutlined />,
|
|
||||||
label: (
|
|
||||||
<span className="sub-title">
|
|
||||||
{intl?.formatMessage?.({ id: 'common.appearance' })}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
onClick: () => {
|
|
||||||
opts.runtimeConfig.toggleTheme();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
const userMenu = {
|
const userMenu = {
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
className: collapsed
|
className: collapsed
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface MenuItem {
|
|||||||
interface SiderMenuProps {
|
interface SiderMenuProps {
|
||||||
menuData: MenuItem[];
|
menuData: MenuItem[];
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
initialState: Global.InitialState;
|
initialState: Global.InitialStateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = createStyles(({ css, token }) => {
|
const useStyles = createStyles(({ css, token }) => {
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export default {
|
|||||||
'Waiting for the download to complete...',
|
'Waiting for the download to complete...',
|
||||||
'resources.filter.worker': 'Filter by worker',
|
'resources.filter.worker': 'Filter by worker',
|
||||||
'resources.filter.source': 'Filter by Source',
|
'resources.filter.source': 'Filter by Source',
|
||||||
|
'resources.filter.status': 'Filter by Status',
|
||||||
'resources.modelfiles.delete.tips': 'Also delete the file from disk',
|
'resources.modelfiles.delete.tips': 'Also delete the file from disk',
|
||||||
'resources.modelfiles.copy.tips': 'Copy Full Path',
|
'resources.modelfiles.copy.tips': 'Copy Full Path',
|
||||||
'resources.filter.path': 'Filter by path',
|
'resources.filter.path': 'Filter by path',
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export default {
|
|||||||
'ダウンロード完了を待っています...',
|
'ダウンロード完了を待っています...',
|
||||||
'resources.filter.worker': 'ワーカーでフィルタ',
|
'resources.filter.worker': 'ワーカーでフィルタ',
|
||||||
'resources.filter.source': 'ソースでフィルタ',
|
'resources.filter.source': 'ソースでフィルタ',
|
||||||
|
'resources.filter.status': 'Filter by Status',
|
||||||
'resources.modelfiles.delete.tips': 'ディスクからファイルも削除します',
|
'resources.modelfiles.delete.tips': 'ディスクからファイルも削除します',
|
||||||
'resources.modelfiles.copy.tips': 'フルパスをコピー',
|
'resources.modelfiles.copy.tips': 'フルパスをコピー',
|
||||||
'resources.filter.path': 'パスでフィルタ',
|
'resources.filter.path': 'パスでフィルタ',
|
||||||
@@ -105,5 +106,6 @@ export default {
|
|||||||
// 8. 'resources.register.install.title': 'Install GPUStack on {os}',
|
// 8. 'resources.register.install.title': 'Install GPUStack on {os}',
|
||||||
// 9. 'resources.register.download':'Download and install the <a>installer</a>. Only supported: {versions}.',
|
// 9. 'resources.register.download':'Download and install the <a>installer</a>. Only supported: {versions}.',
|
||||||
// 10. 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
|
// 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 ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export default {
|
|||||||
'resources.modelfiles.storagePath.holder': 'Ожидание завершения загрузки...',
|
'resources.modelfiles.storagePath.holder': 'Ожидание завершения загрузки...',
|
||||||
'resources.filter.worker': 'Фильтровать по узлу',
|
'resources.filter.worker': 'Фильтровать по узлу',
|
||||||
'resources.filter.source': 'Фильтровать по источнику',
|
'resources.filter.source': 'Фильтровать по источнику',
|
||||||
|
'resources.filter.status': 'Фильтровать по статусу',
|
||||||
'resources.modelfiles.delete.tips': 'Также удалить файл с диска',
|
'resources.modelfiles.delete.tips': 'Также удалить файл с диска',
|
||||||
'resources.modelfiles.copy.tips': 'Скопировать полный путь',
|
'resources.modelfiles.copy.tips': 'Скопировать полный путь',
|
||||||
'resources.filter.path': 'Фильтрация по пути',
|
'resources.filter.path': 'Фильтрация по пути',
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export default {
|
|||||||
'resources.modelfiles.storagePath.holder': '等待下载完成...',
|
'resources.modelfiles.storagePath.holder': '等待下载完成...',
|
||||||
'resources.filter.worker': '按 Worker 筛选',
|
'resources.filter.worker': '按 Worker 筛选',
|
||||||
'resources.filter.source': '按来源筛选',
|
'resources.filter.source': '按来源筛选',
|
||||||
|
'resources.filter.status': '按状态筛选',
|
||||||
'resources.modelfiles.delete.tips': '同时从磁盘删除文件',
|
'resources.modelfiles.delete.tips': '同时从磁盘删除文件',
|
||||||
'resources.modelfiles.copy.tips': '复制完整路径',
|
'resources.modelfiles.copy.tips': '复制完整路径',
|
||||||
'resources.filter.path': '路径查询',
|
'resources.filter.path': '路径查询',
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const clusterActionList = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'details',
|
key: 'details',
|
||||||
label: 'common.button.view',
|
label: 'common.button.detail',
|
||||||
icon: icons.DetailInfo
|
icon: icons.DetailInfo
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ import {
|
|||||||
} from '../config/types';
|
} from '../config/types';
|
||||||
import '../style/instance-item.less';
|
import '../style/instance-item.less';
|
||||||
|
|
||||||
|
interface InstanceItemProps {
|
||||||
|
instanceData: ModelInstanceListItem;
|
||||||
|
workerList: WorkerListItem[];
|
||||||
|
modelData?: any;
|
||||||
|
defaultOpenId: string;
|
||||||
|
handleChildSelect: (val: string, item: ModelInstanceListItem) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const fieldList = [
|
const fieldList = [
|
||||||
{
|
{
|
||||||
label: 'CPU',
|
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 = [
|
const childActionList = [
|
||||||
{
|
{
|
||||||
label: 'common.button.viewlog',
|
label: 'common.button.viewlog',
|
||||||
|
|||||||
@@ -2,86 +2,6 @@ import { StatusMaps } from '@/config';
|
|||||||
import { EditOutlined } from '@ant-design/icons';
|
import { EditOutlined } from '@ant-design/icons';
|
||||||
import _ from 'lodash';
|
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 = [
|
export const backendTipsList = [
|
||||||
{
|
{
|
||||||
title: 'vLLM',
|
title: 'vLLM',
|
||||||
|
|||||||
@@ -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<ModelInstanceListItem>({
|
||||||
|
fetchAPI: queryModelsInstances,
|
||||||
|
polling: false,
|
||||||
|
API: MODEL_INSTANCE_API,
|
||||||
|
defaultQueryParams: {
|
||||||
|
worker_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
const [clusterList, setClusterList] = useState<Global.BaseOption<number>[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
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 <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>;
|
||||||
|
}
|
||||||
|
return <div></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (value: string) => {
|
||||||
|
handleQueryChange({ state: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useInstanceColumns({
|
||||||
|
clusterList
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('columns changed!');
|
||||||
|
}, [columns]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getClusterList();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterBar
|
||||||
|
marginBottom={22}
|
||||||
|
marginTop={0}
|
||||||
|
buttonText={intl.formatMessage({ id: 'resources.button.create' })}
|
||||||
|
handleSearch={handleSearch}
|
||||||
|
handleSelectChange={handleSelectChange}
|
||||||
|
handleInputChange={handleNameChange}
|
||||||
|
showDeleteButton={false}
|
||||||
|
showPrimaryButton={false}
|
||||||
|
selectHolder={intl.formatMessage({ id: 'resources.filter.status' })}
|
||||||
|
showSelect={true}
|
||||||
|
selectOptions={statusList}
|
||||||
|
width={{ input: 300 }}
|
||||||
|
></FilterBar>
|
||||||
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
tableLayout={dataSource.loadend ? 'auto' : 'fixed'}
|
||||||
|
dataSource={dataSource.dataList}
|
||||||
|
loading={dataSource.loading}
|
||||||
|
rowKey="id"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
pagination={{
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSize: queryParams.perPage,
|
||||||
|
current: queryParams.page,
|
||||||
|
total: dataSource.total,
|
||||||
|
hideOnSinglePage: queryParams.perPage === 10,
|
||||||
|
onChange: handlePageChange
|
||||||
|
}}
|
||||||
|
></Table>
|
||||||
|
</ConfigProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkerDetailContent;
|
||||||
@@ -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<WorkerDetailModalProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
currentData
|
||||||
|
}) => {
|
||||||
|
const handleOnClose = () => {
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GSDrawer
|
||||||
|
width={'calc(100vw - 200px)'}
|
||||||
|
title={`${currentData?.name}`}
|
||||||
|
open={open}
|
||||||
|
destroyOnClose={true}
|
||||||
|
onClose={handleOnClose}
|
||||||
|
>
|
||||||
|
<WorkerDetailContent worker_id={currentData?.id} />
|
||||||
|
</GSDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkerDetailModal;
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
import { ListItem } from '../config/types';
|
import { ListItem } from '../config/types';
|
||||||
import useWorkerColumns from '../hooks/use-worker-columns';
|
import useWorkerColumns from '../hooks/use-worker-columns';
|
||||||
import UpdateLabels from './update-labels';
|
import UpdateLabels from './update-labels';
|
||||||
|
import WorkerDetailModal from './worker-detail-modal';
|
||||||
|
|
||||||
const Workers: React.FC = () => {
|
const Workers: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@@ -55,6 +56,13 @@ const Workers: React.FC = () => {
|
|||||||
list: [],
|
list: [],
|
||||||
data: {}
|
data: {}
|
||||||
});
|
});
|
||||||
|
const [workerDetailStatus, setWorkerDetailStatus] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
currentData: ListItem | null;
|
||||||
|
}>({
|
||||||
|
open: false,
|
||||||
|
currentData: null
|
||||||
|
});
|
||||||
|
|
||||||
const getClusterList = async () => {
|
const getClusterList = async () => {
|
||||||
try {
|
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) => {
|
const handleSelect = useMemoizedFn((val: any, record: ListItem) => {
|
||||||
if (val === 'edit') {
|
if (val === 'edit') {
|
||||||
handleUpdateLabels(record);
|
handleUpdateLabels(record);
|
||||||
@@ -126,6 +141,9 @@ const Workers: React.FC = () => {
|
|||||||
if (val === 'delete') {
|
if (val === 'delete') {
|
||||||
handleDelete(record);
|
handleDelete(record);
|
||||||
}
|
}
|
||||||
|
if (val === 'details') {
|
||||||
|
handleViewDetail(record);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderEmpty = (type?: string) => {
|
const renderEmpty = (type?: string) => {
|
||||||
@@ -149,8 +167,8 @@ const Workers: React.FC = () => {
|
|||||||
|
|
||||||
const columns = useWorkerColumns({
|
const columns = useWorkerColumns({
|
||||||
clusterData,
|
clusterData,
|
||||||
dataSource,
|
loadend: dataSource.loadend,
|
||||||
extraStatus,
|
firstLoad: extraStatus.firstLoad,
|
||||||
handleSelect
|
handleSelect
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -216,6 +234,13 @@ const Workers: React.FC = () => {
|
|||||||
labels: updateLabelsData.data.labels
|
labels: updateLabelsData.data.labels
|
||||||
}}
|
}}
|
||||||
></UpdateLabels>
|
></UpdateLabels>
|
||||||
|
<WorkerDetailModal
|
||||||
|
open={workerDetailStatus.open}
|
||||||
|
currentData={workerDetailStatus.currentData}
|
||||||
|
onClose={() =>
|
||||||
|
setWorkerDetailStatus({ currentData: null, open: false })
|
||||||
|
}
|
||||||
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<GPUIndexWrapper>
|
||||||
|
{_.chunk(gpuIndexes, 8).map((item: number[], index: number) => {
|
||||||
|
return <span key={index}>{item.join(',')}</span>;
|
||||||
|
})}
|
||||||
|
</GPUIndexWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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)}
|
||||||
|
<span>(main)</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
renderGpuIndexs(list)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'models.table.vram.allocated',
|
||||||
|
locale: true,
|
||||||
|
key: 'vram',
|
||||||
|
render: ({ rowIndex, row, dataList }) => {
|
||||||
|
return convertFileSize(row.vram, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const calcTotalVram = (vram: Record<string, number>) => {
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<SimpleTabel
|
||||||
|
rowKey="worker_name"
|
||||||
|
columns={distributeCols}
|
||||||
|
dataSource={[...mainWorker]}
|
||||||
|
></SimpleTabel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<TooltipOverlayScroller
|
||||||
|
toolTipProps={{
|
||||||
|
trigger: 'hover',
|
||||||
|
overlayInnerStyle: {
|
||||||
|
width: 'max-content',
|
||||||
|
maxWidth: '520px',
|
||||||
|
minWidth: '400px'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
title={renderDistributedServer(severList, instanceData)}
|
||||||
|
>
|
||||||
|
<ThemeTag
|
||||||
|
opacity={0.75}
|
||||||
|
color="processing"
|
||||||
|
style={{
|
||||||
|
marginRight: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
maxWidth: '100%',
|
||||||
|
minWidth: 50,
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderRadius: 12
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InfoCircleOutlined className="m-r-5" />
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'models.table.acrossworker'
|
||||||
|
})}
|
||||||
|
</ThemeTag>
|
||||||
|
</TooltipOverlayScroller>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Tooltip
|
||||||
|
overlayInnerStyle={{ paddingInline: 12 }}
|
||||||
|
title={<InfoColumn fieldList={fieldList} data={offloadData}></InfoColumn>}
|
||||||
|
>
|
||||||
|
<ThemeTag
|
||||||
|
opacity={0.75}
|
||||||
|
color="cyan"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
maxWidth: '100%',
|
||||||
|
minWidth: 50,
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderRadius: 12
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InfoCircleOutlined className="m-r-5" />
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'models.table.cpuoffload'
|
||||||
|
})}
|
||||||
|
</ThemeTag>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const InstanceStatusTag: React.FC<{
|
||||||
|
instanceData: ListItem;
|
||||||
|
}> = (props) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { instanceData } = props;
|
||||||
|
if (!instanceData.state) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StatusTag
|
||||||
|
download={
|
||||||
|
instanceData.state === InstanceStatusMap.Downloading
|
||||||
|
? { percent: instanceData.download_progress }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
extra={
|
||||||
|
instanceData.state === InstanceStatusMap.Error &&
|
||||||
|
instanceData.worker_id ? (
|
||||||
|
<Button type="link" size="small" style={{ paddingLeft: 0 }}>
|
||||||
|
{intl.formatMessage({ id: 'models.list.more.logs' })}
|
||||||
|
</Button>
|
||||||
|
) : 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<number>[];
|
||||||
|
}): ColumnsType<ListItem> => {
|
||||||
|
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) => (
|
||||||
|
<AutoTooltip ghost maxWidth={240}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Worker',
|
||||||
|
dataIndex: 'worker_name',
|
||||||
|
render: (text: string, record: ListItem) => (
|
||||||
|
<AutoTooltip ghost>{text}</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'models.title' }),
|
||||||
|
dataIndex: 'model_name',
|
||||||
|
render: (text: string, record: ListItem) => (
|
||||||
|
<AutoTooltip ghost>{text}</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'clusters.title' }),
|
||||||
|
dataIndex: 'cluster_id',
|
||||||
|
render: (text: number, record: ListItem) => (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{clusterList.find((item) => item.value === text)?.label}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.status' }),
|
||||||
|
dataIndex: 'state',
|
||||||
|
render: (text: string, record: ListItem) => (
|
||||||
|
<StatusWrapper>
|
||||||
|
<OffloadInfo instanceData={record} />
|
||||||
|
<DistributionInfo instanceData={record} />
|
||||||
|
<InstanceStatusTag instanceData={record} />
|
||||||
|
</StatusWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}, [clusterList]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useInstanceColumns;
|
||||||
@@ -17,7 +17,7 @@ import { ColumnsType } from 'antd/lib/table';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { WorkerStatusMapValue, status } from '../config';
|
import { WorkerStatusMap, WorkerStatusMapValue, status } from '../config';
|
||||||
import { Filesystem, GPUDeviceItem, ListItem } from '../config/types';
|
import { Filesystem, GPUDeviceItem, ListItem } from '../config/types';
|
||||||
|
|
||||||
const LabelsWrapper = styled.div`
|
const LabelsWrapper = styled.div`
|
||||||
@@ -28,6 +28,11 @@ const LabelsWrapper = styled.div`
|
|||||||
|
|
||||||
const ActionList = [
|
const ActionList = [
|
||||||
{ label: 'common.button.edit', key: 'edit', icon: <EditOutlined /> },
|
{ label: 'common.button.edit', key: 'edit', icon: <EditOutlined /> },
|
||||||
|
{
|
||||||
|
label: 'common.button.detail',
|
||||||
|
key: 'details',
|
||||||
|
icon: <IconFont type="icon-detail-info" />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'common.button.logs',
|
label: 'common.button.logs',
|
||||||
locale: false,
|
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 = [
|
const fieldList = [
|
||||||
{
|
{
|
||||||
label: 'resources.table.total',
|
label: 'resources.table.total',
|
||||||
@@ -115,14 +129,14 @@ const VRAMCell = ({
|
|||||||
devices,
|
devices,
|
||||||
intl,
|
intl,
|
||||||
rIndex,
|
rIndex,
|
||||||
dataSource,
|
loadend,
|
||||||
extraStatus
|
firstLoad
|
||||||
}: {
|
}: {
|
||||||
devices: GPUDeviceItem[];
|
devices: GPUDeviceItem[];
|
||||||
intl: any;
|
intl: any;
|
||||||
rIndex: number;
|
rIndex: number;
|
||||||
dataSource: any;
|
loadend: boolean;
|
||||||
extraStatus: any;
|
firstLoad: boolean;
|
||||||
}) => (
|
}) => (
|
||||||
<span className="flex-column flex-gap-2">
|
<span className="flex-column flex-gap-2">
|
||||||
{_.map(
|
{_.map(
|
||||||
@@ -133,12 +147,7 @@ const VRAMCell = ({
|
|||||||
[{item.index}]
|
[{item.index}]
|
||||||
</span>
|
</span>
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
defaultOpen={
|
defaultOpen={rIndex === 0 && index === 0 && loadend && firstLoad}
|
||||||
rIndex === 0 &&
|
|
||||||
index === 0 &&
|
|
||||||
dataSource.loadend &&
|
|
||||||
extraStatus.firstLoad
|
|
||||||
}
|
|
||||||
percent={
|
percent={
|
||||||
item.memory?.used
|
item.memory?.used
|
||||||
? _.round(item.memory?.utilization_rate, 0)
|
? _.round(item.memory?.utilization_rate, 0)
|
||||||
@@ -189,16 +198,16 @@ const StorageCell = ({ files }: { files: Filesystem[] }) => {
|
|||||||
|
|
||||||
const useWorkerColumns = ({
|
const useWorkerColumns = ({
|
||||||
clusterData,
|
clusterData,
|
||||||
dataSource,
|
loadend,
|
||||||
extraStatus,
|
firstLoad,
|
||||||
handleSelect
|
handleSelect
|
||||||
}: {
|
}: {
|
||||||
clusterData: {
|
clusterData: {
|
||||||
list: Global.BaseOption<number>[];
|
list: Global.BaseOption<number>[];
|
||||||
data: Record<number, string>;
|
data: Record<number, string>;
|
||||||
};
|
};
|
||||||
dataSource: any;
|
loadend: boolean;
|
||||||
extraStatus: any;
|
firstLoad: boolean;
|
||||||
handleSelect: (action: string, record: ListItem) => void;
|
handleSelect: (action: string, record: ListItem) => void;
|
||||||
}): ColumnsType<ListItem> => {
|
}): ColumnsType<ListItem> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -290,8 +299,8 @@ const useWorkerColumns = ({
|
|||||||
devices={record?.status?.gpu_devices}
|
devices={record?.status?.gpu_devices}
|
||||||
intl={intl}
|
intl={intl}
|
||||||
rIndex={rIndex}
|
rIndex={rIndex}
|
||||||
dataSource={dataSource}
|
loadend={loadend}
|
||||||
extraStatus={extraStatus}
|
firstLoad={firstLoad}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -305,13 +314,13 @@ const useWorkerColumns = ({
|
|||||||
key: 'operation',
|
key: 'operation',
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<DropdownButtons
|
<DropdownButtons
|
||||||
items={ActionList}
|
items={setActions(record)}
|
||||||
onSelect={(val) => handleSelect(val, record)}
|
onSelect={(val) => handleSelect(val, record)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[intl, clusterData, dataSource, extraStatus, handleSelect]
|
[intl, clusterData, loadend, firstLoad, handleSelect]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user