feat: add instance view
This commit is contained in:
+1
-1
@@ -114,7 +114,7 @@ export default [
|
|||||||
selectedIcon: 'icon-rocket-launch-fill',
|
selectedIcon: 'icon-rocket-launch-fill',
|
||||||
defaultIcon: 'icon-rocket-launch1',
|
defaultIcon: 'icon-rocket-launch1',
|
||||||
access: 'canSeeAdmin',
|
access: 'canSeeAdmin',
|
||||||
component: './llmodels/deployments'
|
component: './llmodels/index'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'routes',
|
name: 'routes',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
|
|||||||
import './iconfont/iconfont.js';
|
import './iconfont/iconfont.js';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: ''
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_7zu9s75km1r.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -318,13 +318,15 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
onScroll={handleOnScroll}
|
onScroll={handleOnScroll}
|
||||||
></LogsList>
|
></LogsList>
|
||||||
</div>
|
</div>
|
||||||
<Spin
|
{loading && (
|
||||||
size="middle"
|
<Spin
|
||||||
spinning={loading}
|
size="middle"
|
||||||
className={classNames({
|
spinning={loading}
|
||||||
loading: loading
|
className={classNames({
|
||||||
})}
|
loading: loading
|
||||||
></Spin>
|
})}
|
||||||
|
></Spin>
|
||||||
|
)}
|
||||||
{totalPage > 1 && (
|
{totalPage > 1 && (
|
||||||
<div className="pg">
|
<div className="pg">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -42,5 +42,6 @@ export const PaginationKey = {
|
|||||||
ModelFiles: 'ModelFiles',
|
ModelFiles: 'ModelFiles',
|
||||||
Users: 'Users',
|
Users: 'Users',
|
||||||
APIKeys: 'APIKeys',
|
APIKeys: 'APIKeys',
|
||||||
Credentials: 'Credentials'
|
Credentials: 'Credentials',
|
||||||
|
Instances: 'Instances'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -249,6 +249,10 @@ export default function useTableFetch<T>(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cancelChunkRequest = () => {
|
||||||
|
chunkRequestRef.current?.current?.cancel?.();
|
||||||
|
};
|
||||||
|
|
||||||
const createTableListChunkRequest = async (params?: any) => {
|
const createTableListChunkRequest = async (params?: any) => {
|
||||||
if (!API || !watch) return;
|
if (!API || !watch) return;
|
||||||
chunkRequestRef.current?.current?.cancel?.();
|
chunkRequestRef.current?.current?.cancel?.();
|
||||||
@@ -289,7 +293,7 @@ export default function useTableFetch<T>(
|
|||||||
const handleQueryChange = async (
|
const handleQueryChange = async (
|
||||||
params: any,
|
params: any,
|
||||||
options?: {
|
options?: {
|
||||||
paginate?: boolean;
|
paginate?: boolean; // it's change for pagination
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
// cancel previous chunk request so that we can create a new one with updated params
|
// cancel previous chunk request so that we can create a new one with updated params
|
||||||
@@ -459,6 +463,8 @@ export default function useTableFetch<T>(
|
|||||||
handleSearch,
|
handleSearch,
|
||||||
handleQueryChange,
|
handleQueryChange,
|
||||||
loadMore,
|
loadMore,
|
||||||
|
cancelChunkRequest,
|
||||||
|
createTableListChunkRequest,
|
||||||
handleNameChange
|
handleNameChange
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default {
|
|||||||
'menu.models.benchmark': 'Benchmarks',
|
'menu.models.benchmark': 'Benchmarks',
|
||||||
'menu.models.benchmarkDetail': 'Benchmark Details',
|
'menu.models.benchmarkDetail': 'Benchmark Details',
|
||||||
'menu.models.providers': 'Providers',
|
'menu.models.providers': 'Providers',
|
||||||
|
'menu.models.instances': 'Instances',
|
||||||
'menu.models.routes': 'Routes',
|
'menu.models.routes': 'Routes',
|
||||||
'menu.modelCatalog': 'Catalog',
|
'menu.modelCatalog': 'Catalog',
|
||||||
'menu.resources': 'Resources',
|
'menu.resources': 'Resources',
|
||||||
|
|||||||
@@ -279,5 +279,7 @@ export default {
|
|||||||
'models.form.maxContextLength': 'Maximum Context Length',
|
'models.form.maxContextLength': 'Maximum Context Length',
|
||||||
'models.form.backend.helperText':
|
'models.form.backend.helperText':
|
||||||
'Not enabled yet. Will be enabled after deployment. ',
|
'Not enabled yet. Will be enabled after deployment. ',
|
||||||
'models.table.instance.benchmark': 'Run Benchmark'
|
'models.table.instance.benchmark': 'Run Benchmark',
|
||||||
|
'models.table.modelView': 'Model View',
|
||||||
|
'models.table.instanceView': 'Instance View'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,5 +48,9 @@ export default {
|
|||||||
'noresult.providers.nofound': 'No matching providers found.',
|
'noresult.providers.nofound': 'No matching providers found.',
|
||||||
'noresult.routes.title': 'No Routes',
|
'noresult.routes.title': 'No Routes',
|
||||||
'noresult.routes.subTitle': 'No routes have been added yet.',
|
'noresult.routes.subTitle': 'No routes have been added yet.',
|
||||||
'noresult.routes.nofound': 'No matching routes found.'
|
'noresult.routes.nofound': 'No matching routes found.',
|
||||||
|
'noresult.instances.title': 'No instances',
|
||||||
|
'noresult.instances.subTitle':
|
||||||
|
'There are no running model instances. Deploy a model to create one automatically.',
|
||||||
|
'noresult.instances.nofound': 'No matching instances found.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default {
|
|||||||
'menu.models.benchmarkDetail': 'Benchmark Details',
|
'menu.models.benchmarkDetail': 'Benchmark Details',
|
||||||
'menu.modelCatalog': 'カタログ',
|
'menu.modelCatalog': 'カタログ',
|
||||||
'menu.models.providers': 'Providers',
|
'menu.models.providers': 'Providers',
|
||||||
|
'menu.models.instances': 'Instances',
|
||||||
'menu.models.routes': 'Routes',
|
'menu.models.routes': 'Routes',
|
||||||
'menu.resources': 'リソース',
|
'menu.resources': 'リソース',
|
||||||
'menu.apikeys': 'APIキー',
|
'menu.apikeys': 'APIキー',
|
||||||
|
|||||||
@@ -279,7 +279,9 @@ export default {
|
|||||||
'models.form.maxContextLength': 'Maximum Context Length',
|
'models.form.maxContextLength': 'Maximum Context Length',
|
||||||
'models.form.backend.helperText':
|
'models.form.backend.helperText':
|
||||||
'Not enabled yet. Will be enabled after deployment. ',
|
'Not enabled yet. Will be enabled after deployment. ',
|
||||||
'models.table.instance.benchmark': 'Run Benchmark'
|
'models.table.instance.benchmark': 'Run Benchmark',
|
||||||
|
'models.table.modelView': 'Model View',
|
||||||
|
'models.table.instanceView': 'Instance View'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -380,4 +382,6 @@ export default {
|
|||||||
// 78. 'models.table.instance.benchmark': 'Run Benchmark'
|
// 78. 'models.table.instance.benchmark': 'Run Benchmark'
|
||||||
// 77. 'models.form.enableModelRoute': 'Enable Model Route',
|
// 77. 'models.form.enableModelRoute': 'Enable Model Route',
|
||||||
// 78. 'models.form.enableModelRoute.tips': 'Enable Model Route',
|
// 78. 'models.form.enableModelRoute.tips': 'Enable Model Route',
|
||||||
|
// 79. 'models.table.modelView': 'Model View',
|
||||||
|
// 80. 'models.table.instanceView': 'Instance View'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -48,5 +48,9 @@ export default {
|
|||||||
'noresult.providers.nofound': 'No matching providers found.',
|
'noresult.providers.nofound': 'No matching providers found.',
|
||||||
'noresult.routes.title': 'No Routes',
|
'noresult.routes.title': 'No Routes',
|
||||||
'noresult.routes.subTitle': 'No routes have been added yet.',
|
'noresult.routes.subTitle': 'No routes have been added yet.',
|
||||||
'noresult.routes.nofound': 'No matching routes found.'
|
'noresult.routes.nofound': 'No matching routes found.',
|
||||||
|
'noresult.instances.title': 'No instances',
|
||||||
|
'noresult.instances.subTitle':
|
||||||
|
'There are no running model instances. Deploy a model to create one automatically.',
|
||||||
|
'noresult.instances.nofound': 'No matching instances found.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default {
|
|||||||
'menu.models.benchmark': 'Benchmarks',
|
'menu.models.benchmark': 'Benchmarks',
|
||||||
'menu.models.benchmarkDetail': 'Benchmark Details',
|
'menu.models.benchmarkDetail': 'Benchmark Details',
|
||||||
'menu.models.providers': 'Providers',
|
'menu.models.providers': 'Providers',
|
||||||
|
'menu.models.instances': 'Instances',
|
||||||
'menu.models.routes': 'Routes',
|
'menu.models.routes': 'Routes',
|
||||||
'menu.modelCatalog': 'Каталог',
|
'menu.modelCatalog': 'Каталог',
|
||||||
'menu.resources': 'Ресурсы',
|
'menu.resources': 'Ресурсы',
|
||||||
@@ -45,4 +46,5 @@ export default {
|
|||||||
// 2. 'menu.models.benchmarkDetail': 'Benchmark Details',
|
// 2. 'menu.models.benchmarkDetail': 'Benchmark Details',
|
||||||
// 3. 'menu.models.providers': 'Providers',
|
// 3. 'menu.models.providers': 'Providers',
|
||||||
// 4. 'menu.models.routes': 'Routes',
|
// 4. 'menu.models.routes': 'Routes',
|
||||||
|
// 5. 'menu.models.instances': 'Instances',
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -283,7 +283,9 @@ export default {
|
|||||||
'models.form.maxContextLength': 'Maximum Context Length',
|
'models.form.maxContextLength': 'Maximum Context Length',
|
||||||
'models.form.backend.helperText':
|
'models.form.backend.helperText':
|
||||||
'Not enabled yet. Will be enabled after deployment. ',
|
'Not enabled yet. Will be enabled after deployment. ',
|
||||||
'models.table.instance.benchmark': 'Run Benchmark'
|
'models.table.instance.benchmark': 'Run Benchmark',
|
||||||
|
'models.table.modelView': 'Model View',
|
||||||
|
'models.table.instanceView': 'Instance View'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -294,4 +296,6 @@ export default {
|
|||||||
// 3. 'models.form.enableModelRoute.tips': 'Enable Model Route',
|
// 3. 'models.form.enableModelRoute.tips': 'Enable Model Route',
|
||||||
// 4. 'models.form.backend.vllm': 'Built-in support for NVIDIA, AMD, Ascend, Hygon, Moore Threads, Iluvatar, MetaX, T-Head PPU devices.',
|
// 4. 'models.form.backend.vllm': 'Built-in support for NVIDIA, AMD, Ascend, Hygon, Moore Threads, Iluvatar, MetaX, T-Head PPU devices.',
|
||||||
// 5. 'models.form.backend.sglang': 'Built-in support for NVIDIA, AMD, Ascend, Moore Threads, MetaX, T-Head PPU devices.',
|
// 5. 'models.form.backend.sglang': 'Built-in support for NVIDIA, AMD, Ascend, Moore Threads, MetaX, T-Head PPU devices.',
|
||||||
|
// 6. 'models.table.modelView': 'Model View',
|
||||||
|
// 7. 'models.table.instanceView': 'Instance View'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -49,7 +49,11 @@ export default {
|
|||||||
'noresult.providers.nofound': 'No matching providers found.',
|
'noresult.providers.nofound': 'No matching providers found.',
|
||||||
'noresult.routes.title': 'No Routes',
|
'noresult.routes.title': 'No Routes',
|
||||||
'noresult.routes.subTitle': 'No routes have been added yet.',
|
'noresult.routes.subTitle': 'No routes have been added yet.',
|
||||||
'noresult.routes.nofound': 'No matching routes found.'
|
'noresult.routes.nofound': 'No matching routes found.',
|
||||||
|
'noresult.instances.title': 'No instances',
|
||||||
|
'noresult.instances.subTitle':
|
||||||
|
'There are no running model instances. Deploy a model to create one automatically.',
|
||||||
|
'noresult.instances.nofound': 'No matching instances found.'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
@@ -66,4 +70,7 @@ export default {
|
|||||||
// 8. 'noresult.routes.title': 'No Routes',
|
// 8. 'noresult.routes.title': 'No Routes',
|
||||||
// 9. 'noresult.routes.subTitle': 'No routes have been added yet.',
|
// 9. 'noresult.routes.subTitle': 'No routes have been added yet.',
|
||||||
// 10. 'noresult.routes.nofound': 'No matching routes found.'
|
// 10. 'noresult.routes.nofound': 'No matching routes found.'
|
||||||
|
// 11. 'noresult.instances.title': 'No instances',
|
||||||
|
// 12. 'noresult.instances.subTitle': 'There are no running model instances. Deploy a model to create one automatically.',
|
||||||
|
// 13. 'noresult.instances.nofound': 'No matching instances found.'
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default {
|
|||||||
'menu.models.benchmark': '基准测试',
|
'menu.models.benchmark': '基准测试',
|
||||||
'menu.models.benchmarkDetail': '基准测试详情',
|
'menu.models.benchmarkDetail': '基准测试详情',
|
||||||
'menu.models.providers': '提供商',
|
'menu.models.providers': '提供商',
|
||||||
|
'menu.models.instances': '实例',
|
||||||
'menu.models.routes': '路由',
|
'menu.models.routes': '路由',
|
||||||
'menu.modelCatalog': '模型库',
|
'menu.modelCatalog': '模型库',
|
||||||
'menu.models.catalog': '模型库',
|
'menu.models.catalog': '模型库',
|
||||||
|
|||||||
@@ -263,5 +263,7 @@ export default {
|
|||||||
'models.form.readyWorkers': '节点就绪',
|
'models.form.readyWorkers': '节点就绪',
|
||||||
'models.form.maxContextLength': '最大上下文长度',
|
'models.form.maxContextLength': '最大上下文长度',
|
||||||
'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用',
|
'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用',
|
||||||
'models.table.instance.benchmark': '运行基准测试'
|
'models.table.instance.benchmark': '运行基准测试',
|
||||||
|
'models.table.modelView': '模型视图',
|
||||||
|
'models.table.instanceView': '实例视图'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,5 +46,9 @@ export default {
|
|||||||
'noresult.providers.nofound': '未找到匹配的提供商',
|
'noresult.providers.nofound': '未找到匹配的提供商',
|
||||||
'noresult.routes.title': '暂无路由',
|
'noresult.routes.title': '暂无路由',
|
||||||
'noresult.routes.subTitle': '尚未添加任何路由。',
|
'noresult.routes.subTitle': '尚未添加任何路由。',
|
||||||
'noresult.routes.nofound': '未找到匹配的路由'
|
'noresult.routes.nofound': '未找到匹配的路由',
|
||||||
|
'noresult.instances.title': '暂无实例',
|
||||||
|
'noresult.instances.subTitle':
|
||||||
|
'当前没有运行中的模型实例,部署模型后将自动创建。',
|
||||||
|
'noresult.instances.nofound': '未找到匹配的实例'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { clusterDetailAtom } from '@/atoms/clusters';
|
import { clusterDetailAtom } from '@/atoms/clusters';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import Deployments from '@/pages/llmodels/index';
|
import Deployments from '@/pages/llmodels/deployments';
|
||||||
import GPUList from '@/pages/resources/components/gpus';
|
import GPUList from '@/pages/resources/components/gpus';
|
||||||
import WorkerList from '@/pages/resources/components/workers';
|
import WorkerList from '@/pages/resources/components/workers';
|
||||||
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import BaseSelect from '@/components/seal-form/base/select';
|
||||||
|
import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Input, Space } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { modelCategories } from '../config';
|
||||||
|
import useFilterStatus from '../hooks/use-filter-status';
|
||||||
|
|
||||||
|
interface LeftFiltersProps {
|
||||||
|
handleNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
handleClusterChange: (value: number) => void;
|
||||||
|
handleStatusChange: (value: string) => void;
|
||||||
|
handleSearch: () => void;
|
||||||
|
handleCategoryChange: (value: string) => void;
|
||||||
|
clusterList: Global.BaseOption<number>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeftFilters: React.FC<LeftFiltersProps> = (props) => {
|
||||||
|
const {
|
||||||
|
handleNameChange,
|
||||||
|
handleClusterChange,
|
||||||
|
handleStatusChange,
|
||||||
|
handleSearch,
|
||||||
|
handleCategoryChange,
|
||||||
|
clusterList
|
||||||
|
} = props;
|
||||||
|
const { labelRender, optionRender, statusOptions } = useFilterStatus();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Input
|
||||||
|
prefix={
|
||||||
|
<SearchOutlined
|
||||||
|
style={{ color: 'var(--ant-color-text-placeholder)' }}
|
||||||
|
></SearchOutlined>
|
||||||
|
}
|
||||||
|
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||||
|
style={{ width: 200 }}
|
||||||
|
size="large"
|
||||||
|
allowClear
|
||||||
|
onChange={handleNameChange}
|
||||||
|
></Input>
|
||||||
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'models.filter.category'
|
||||||
|
})}
|
||||||
|
style={{ width: 160 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
onChange={handleCategoryChange}
|
||||||
|
options={modelCategories.filter((item) => item.value)}
|
||||||
|
></BaseSelect>
|
||||||
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'clusters.filterBy.cluster'
|
||||||
|
})}
|
||||||
|
style={{ width: 160 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
onChange={handleClusterChange}
|
||||||
|
options={clusterList}
|
||||||
|
></BaseSelect>
|
||||||
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
|
||||||
|
style={{ width: 180 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
optionRender={optionRender}
|
||||||
|
labelRender={labelRender}
|
||||||
|
options={statusOptions}
|
||||||
|
onChange={handleStatusChange}
|
||||||
|
></BaseSelect>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
onClick={handleSearch}
|
||||||
|
icon={<SyncOutlined></SyncOutlined>}
|
||||||
|
></Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LeftFilters;
|
||||||
@@ -2,9 +2,7 @@ import { modelsExpandKeysAtom, modelsSessionAtom } from '@/atoms/models';
|
|||||||
import DeleteModal from '@/components/delete-modal';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
import DropDownActions from '@/components/drop-down-actions';
|
import DropDownActions from '@/components/drop-down-actions';
|
||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
import DropdownButtons from '@/components/drop-down-buttons';
|
||||||
import { PageSize } from '@/components/logs-viewer/config';
|
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
import BaseSelect from '@/components/seal-form/base/select';
|
|
||||||
import SealTable from '@/components/seal-table';
|
import SealTable from '@/components/seal-table';
|
||||||
import { TableOrder } from '@/components/seal-table/types';
|
import { TableOrder } from '@/components/seal-table/types';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
@@ -21,10 +19,10 @@ import { TargetStatusValueMap } from '@/pages/model-routes/config';
|
|||||||
import useOpenPlayground from '@/pages/model-routes/hooks/use-open-playground';
|
import useOpenPlayground from '@/pages/model-routes/hooks/use-open-playground';
|
||||||
import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link';
|
import useGranfanaLink from '@/pages/resources/hooks/use-grafana-link';
|
||||||
import { handleBatchRequest } from '@/utils';
|
import { handleBatchRequest } from '@/utils';
|
||||||
import { DownOutlined, SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Button, Input, Space, message } from 'antd';
|
import { Button, Space, message } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
@@ -36,23 +34,19 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
MODELS_API,
|
MODELS_API,
|
||||||
MODEL_INSTANCE_API,
|
|
||||||
createModel,
|
createModel,
|
||||||
deleteModel,
|
deleteModel,
|
||||||
deleteModelInstance,
|
deleteModelInstance,
|
||||||
queryModelInstancesList,
|
queryModelInstancesList,
|
||||||
updateModel
|
updateModel
|
||||||
} from '../apis';
|
} from '../apis';
|
||||||
import {
|
import { modelSourceMap } from '../config';
|
||||||
InstanceRealtimeLogStatus,
|
|
||||||
modelCategories,
|
|
||||||
modelSourceMap
|
|
||||||
} from '../config';
|
|
||||||
import {
|
import {
|
||||||
ButtonList,
|
ButtonList,
|
||||||
modalConfig,
|
modalConfig,
|
||||||
sourceOptions
|
sourceOptions
|
||||||
} from '../config/button-actions';
|
} from '../config/button-actions';
|
||||||
|
import { useDeploymentsContext } from '../config/deploments-context';
|
||||||
import {
|
import {
|
||||||
FormData,
|
FormData,
|
||||||
ListItem,
|
ListItem,
|
||||||
@@ -60,12 +54,12 @@ import {
|
|||||||
SourceType
|
SourceType
|
||||||
} from '../config/types';
|
} from '../config/types';
|
||||||
import useEditDeployment from '../hooks/use-edit-deployment';
|
import useEditDeployment from '../hooks/use-edit-deployment';
|
||||||
import useFilterStatus from '../hooks/use-filter-status';
|
|
||||||
import useFormInitialValues from '../hooks/use-form-initial-values';
|
|
||||||
import useModelsColumns from '../hooks/use-models-columns';
|
import useModelsColumns from '../hooks/use-models-columns';
|
||||||
|
import useViewInstanceLogs from '../hooks/use-view-instance-logs';
|
||||||
import DeployModal from './deployment/deploy-modal';
|
import DeployModal from './deployment/deploy-modal';
|
||||||
import UpdateModelModal from './deployment/update-modal';
|
import UpdateModelModal from './deployment/update-modal';
|
||||||
import Instances from './instance/instances';
|
import Instances from './instance/instances';
|
||||||
|
import LeftFilters from './left-filters';
|
||||||
import ViewLogsModal from './view-logs-modal';
|
import ViewLogsModal from './view-logs-modal';
|
||||||
interface ModelsProps {
|
interface ModelsProps {
|
||||||
handleSearch: (params?: any) => void;
|
handleSearch: (params?: any) => void;
|
||||||
@@ -136,13 +130,8 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
loadend,
|
loadend,
|
||||||
total
|
total
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { generateFormValues, clusterList, workerList } =
|
||||||
generateFormValues,
|
useDeploymentsContext();
|
||||||
clusterList,
|
|
||||||
getClusterList,
|
|
||||||
getWorkerList,
|
|
||||||
workerList
|
|
||||||
} = useFormInitialValues();
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const page = searchParams.get('page');
|
const page = searchParams.get('page');
|
||||||
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
@@ -165,11 +154,9 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
expandedRowKeys
|
expandedRowKeys
|
||||||
} = useExpandedRowKeys(expandAtom);
|
} = useExpandedRowKeys(expandAtom);
|
||||||
const { handleOpenPlayGround } = useOpenPlayground();
|
const { handleOpenPlayGround } = useOpenPlayground();
|
||||||
const { labelRender, optionRender, handleStatusChange, statusOptions } =
|
|
||||||
useFilterStatus({
|
|
||||||
onStatusChange: onStatusChange
|
|
||||||
});
|
|
||||||
const { watchDataList: targetList } = useWatchList(MODEL_ROUTE_TARGETS);
|
const { watchDataList: targetList } = useWatchList(MODEL_ROUTE_TARGETS);
|
||||||
|
const { openViewLogsModal, openViewLogsModalStatus, closeViewLogsModal } =
|
||||||
|
useViewInstanceLogs();
|
||||||
|
|
||||||
const { goToGrafana, ActionButton } = useGranfanaLink({
|
const { goToGrafana, ActionButton } = useGranfanaLink({
|
||||||
type: 'model'
|
type: 'model'
|
||||||
@@ -189,16 +176,6 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
isGGUF: false,
|
isGGUF: false,
|
||||||
source: modelSourceMap.huggingface_value as SourceType
|
source: modelSourceMap.huggingface_value as SourceType
|
||||||
});
|
});
|
||||||
const [currentInstance, setCurrentInstance] = useState<{
|
|
||||||
url: string;
|
|
||||||
status: string;
|
|
||||||
id?: number | string;
|
|
||||||
modelId?: number | string;
|
|
||||||
tail?: number;
|
|
||||||
}>({
|
|
||||||
url: '',
|
|
||||||
status: ''
|
|
||||||
});
|
|
||||||
const modalRef = useRef<any>(null);
|
const modalRef = useRef<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -208,10 +185,6 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
}, [deleteIds]);
|
}, [deleteIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getData = async () => {
|
|
||||||
await Promise.all([getClusterList(), getWorkerList()]);
|
|
||||||
};
|
|
||||||
getData();
|
|
||||||
return () => {
|
return () => {
|
||||||
setExpandAtom([]);
|
setExpandAtom([]);
|
||||||
};
|
};
|
||||||
@@ -308,11 +281,10 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogModalCancel = useCallback(() => {
|
const handleLogModalCancel = () => {
|
||||||
setOpenLogModal(false);
|
|
||||||
onCancelViewLogs();
|
onCancelViewLogs();
|
||||||
restoreScrollHeight();
|
closeViewLogsModal();
|
||||||
}, [onCancelViewLogs]);
|
};
|
||||||
|
|
||||||
const handleDelete = async (row: any) => {
|
const handleDelete = async (row: any) => {
|
||||||
modalRef.current?.show({
|
modalRef.current?.show({
|
||||||
@@ -352,22 +324,8 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleViewLogs = async (row: any) => {
|
const handleViewLogs = async (row: any) => {
|
||||||
try {
|
openViewLogsModal(row);
|
||||||
setCurrentInstance({
|
onViewLogs();
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteInstace = (row: any) => {
|
const handleDeleteInstace = (row: any) => {
|
||||||
@@ -600,62 +558,14 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
marginBottom={22}
|
marginBottom={22}
|
||||||
marginTop={0}
|
marginTop={0}
|
||||||
left={
|
left={
|
||||||
<Space>
|
<LeftFilters
|
||||||
<Input
|
handleNameChange={handleNameChange}
|
||||||
prefix={
|
handleClusterChange={handleClusterChange}
|
||||||
<SearchOutlined
|
handleCategoryChange={handleCategoryChange}
|
||||||
style={{ color: 'var(--ant-color-text-placeholder)' }}
|
handleStatusChange={onStatusChange}
|
||||||
></SearchOutlined>
|
handleSearch={handleSearch}
|
||||||
}
|
clusterList={clusterList}
|
||||||
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
></LeftFilters>
|
||||||
style={{ width: 200 }}
|
|
||||||
size="large"
|
|
||||||
allowClear
|
|
||||||
onChange={handleNameChange}
|
|
||||||
></Input>
|
|
||||||
<BaseSelect
|
|
||||||
allowClear
|
|
||||||
showSearch={false}
|
|
||||||
placeholder={intl.formatMessage({
|
|
||||||
id: 'models.filter.category'
|
|
||||||
})}
|
|
||||||
style={{ width: 160 }}
|
|
||||||
size="large"
|
|
||||||
maxTagCount={1}
|
|
||||||
onChange={handleCategoryChange}
|
|
||||||
options={modelCategories.filter((item) => item.value)}
|
|
||||||
></BaseSelect>
|
|
||||||
<BaseSelect
|
|
||||||
allowClear
|
|
||||||
showSearch={false}
|
|
||||||
placeholder={intl.formatMessage({
|
|
||||||
id: 'clusters.filterBy.cluster'
|
|
||||||
})}
|
|
||||||
style={{ width: 160 }}
|
|
||||||
size="large"
|
|
||||||
maxTagCount={1}
|
|
||||||
onChange={handleClusterChange}
|
|
||||||
options={clusterList}
|
|
||||||
></BaseSelect>
|
|
||||||
<BaseSelect
|
|
||||||
allowClear
|
|
||||||
showSearch={false}
|
|
||||||
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
|
|
||||||
style={{ width: 180 }}
|
|
||||||
size="large"
|
|
||||||
maxTagCount={1}
|
|
||||||
optionRender={optionRender}
|
|
||||||
labelRender={labelRender}
|
|
||||||
options={statusOptions}
|
|
||||||
onChange={handleStatusChange}
|
|
||||||
></BaseSelect>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
|
||||||
onClick={handleSearch}
|
|
||||||
icon={<SyncOutlined></SyncOutlined>}
|
|
||||||
></Button>
|
|
||||||
</Space>
|
|
||||||
}
|
}
|
||||||
right={
|
right={
|
||||||
<Space size={16}>
|
<Space size={16}>
|
||||||
@@ -720,6 +630,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
pageSize: queryParams.perPage,
|
pageSize: queryParams.perPage,
|
||||||
current: queryParams.page,
|
current: queryParams.page,
|
||||||
total: total,
|
total: total,
|
||||||
|
size: 'middle',
|
||||||
hideOnSinglePage: queryParams.perPage === 10,
|
hideOnSinglePage: queryParams.perPage === 10,
|
||||||
onChange: handlePageChange
|
onChange: handlePageChange
|
||||||
}}
|
}}
|
||||||
@@ -747,12 +658,12 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
onOk={handleCreateModel}
|
onOk={handleCreateModel}
|
||||||
></DeployModal>
|
></DeployModal>
|
||||||
<ViewLogsModal
|
<ViewLogsModal
|
||||||
status={currentInstance.status}
|
status={openViewLogsModalStatus.currentData.status}
|
||||||
url={currentInstance.url}
|
url={openViewLogsModalStatus.currentData.url}
|
||||||
tail={currentInstance.tail}
|
tail={openViewLogsModalStatus.currentData.tail}
|
||||||
id={currentInstance.id}
|
id={openViewLogsModalStatus.currentData.id}
|
||||||
modelId={currentInstance.modelId}
|
modelId={openViewLogsModalStatus.currentData.modelId}
|
||||||
open={openLogModal}
|
open={openViewLogsModalStatus.open}
|
||||||
onCancel={handleLogModalCancel}
|
onCancel={handleLogModalCancel}
|
||||||
></ViewLogsModal>
|
></ViewLogsModal>
|
||||||
<DeleteModal ref={modalRef}></DeleteModal>
|
<DeleteModal ref={modalRef}></DeleteModal>
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ const ViewLogsModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!props.id) return;
|
if (open && props.id) {
|
||||||
if (open) {
|
|
||||||
requestRef.current?.current?.cancel?.();
|
requestRef.current?.current?.cancel?.();
|
||||||
requestRef.current = setChunkRequest({
|
requestRef.current = setChunkRequest({
|
||||||
url: `${MODELS_API}/${props.modelId}/instances`,
|
url: `${MODELS_API}/${props.modelId}/instances`,
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
|
||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
import { ListItem as ModelListItem } from '../config/types';
|
||||||
|
|
||||||
|
interface DeploymentsContextProps {
|
||||||
|
clusterList: Global.BaseOption<
|
||||||
|
number,
|
||||||
|
{
|
||||||
|
provider: string;
|
||||||
|
state: string;
|
||||||
|
is_default: boolean;
|
||||||
|
}
|
||||||
|
>[];
|
||||||
|
workerList: WorkerListItem[];
|
||||||
|
generateFormValues: (
|
||||||
|
data: ModelListItem,
|
||||||
|
gpuOptions: any
|
||||||
|
) => Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeploymentsContext = createContext<DeploymentsContextProps>({
|
||||||
|
clusterList: [],
|
||||||
|
workerList: [],
|
||||||
|
generateFormValues: () => ({})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useDeploymentsContext = () => {
|
||||||
|
const context = useContext(DeploymentsContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useDeploymentsContext must be used within a DeploymentsProvider'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -119,6 +119,7 @@ export interface ModelInstanceListItem {
|
|||||||
cluster_id: number;
|
cluster_id: number;
|
||||||
backend_version?: string;
|
backend_version?: string;
|
||||||
source: string;
|
source: string;
|
||||||
|
categories?: string[];
|
||||||
huggingface_repo_id: string;
|
huggingface_repo_id: string;
|
||||||
huggingface_filename: string;
|
huggingface_filename: string;
|
||||||
ollama_library_model_name: string;
|
ollama_library_model_name: string;
|
||||||
|
|||||||
@@ -1,70 +1,472 @@
|
|||||||
import IconFont from '@/components/icon-font';
|
import TableContext from '@/components/seal-table/table-context';
|
||||||
import { PageContainerInner } from '@/pages/_components/page-box';
|
import { TableOrder } from '@/components/seal-table/types';
|
||||||
import { useIntl } from '@umijs/max';
|
import { PaginationKey } from '@/config/settings';
|
||||||
import { Segmented, Tabs } from 'antd';
|
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
||||||
import { useMemo, useState } from 'react';
|
import { usePaginationStatus } from '@/hooks/use-pagination-status';
|
||||||
import ModelView from './index';
|
import { useTableMultiSort } from '@/hooks/use-table-sort';
|
||||||
import InstanceView from './instance-view';
|
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import qs from 'query-string';
|
||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
|
import {
|
||||||
|
MODELS_API,
|
||||||
|
MODEL_INSTANCE_API,
|
||||||
|
queryModelsInstances,
|
||||||
|
queryModelsList
|
||||||
|
} from './apis';
|
||||||
|
import TableList from './components/table-list';
|
||||||
|
import { ListItem } from './config/types';
|
||||||
|
|
||||||
const TabsValueMap = {
|
const Models = forwardRef((props, ref) => {
|
||||||
ModelView: 'modelView',
|
const { pagination, setPagination } = usePaginationStatus(
|
||||||
InstanceView: 'instanceView'
|
PaginationKey.Deployments
|
||||||
};
|
);
|
||||||
|
const { sortOrder, handleMultiSortChange } = useTableMultiSort();
|
||||||
|
const { setChunkRequest, createAxiosToken } = useSetChunkRequest();
|
||||||
|
const { setChunkRequest: setModelInstanceChunkRequest } =
|
||||||
|
useSetChunkRequest();
|
||||||
|
const [modelInstances, setModelInstances] = useState<any[]>([]);
|
||||||
|
const [dataSource, setDataSource] = useState<{
|
||||||
|
dataList: ListItem[];
|
||||||
|
deletedIds: number[];
|
||||||
|
loading: boolean;
|
||||||
|
loadend: boolean;
|
||||||
|
total: number;
|
||||||
|
}>({
|
||||||
|
dataList: [],
|
||||||
|
deletedIds: [],
|
||||||
|
loading: false,
|
||||||
|
loadend: false,
|
||||||
|
total: 0
|
||||||
|
});
|
||||||
|
const chunkRequedtRef = useRef<any>();
|
||||||
|
const chunkInstanceRequedtRef = useRef<any>();
|
||||||
|
const isPageHidden = useRef(false);
|
||||||
|
const instancesToken = useRef<any>();
|
||||||
|
let axiosToken = createAxiosToken();
|
||||||
|
const [queryParams, setQueryParams] = useState({
|
||||||
|
page: 1,
|
||||||
|
perPage: 10,
|
||||||
|
search: '',
|
||||||
|
cluster_id: 0,
|
||||||
|
categories: [],
|
||||||
|
state: '',
|
||||||
|
sort_by: '',
|
||||||
|
...pagination
|
||||||
|
});
|
||||||
|
|
||||||
const Playground: React.FC = () => {
|
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
||||||
const intl = useIntl();
|
events: ['UPDATE'],
|
||||||
const [activeKey, setActiveKey] = useState(TabsValueMap.ModelView);
|
dataList: dataSource.dataList,
|
||||||
|
setDataList(list, opts?: any) {
|
||||||
|
setDataSource((pre) => {
|
||||||
|
return {
|
||||||
|
total: pre.total,
|
||||||
|
loading: false,
|
||||||
|
loadend: true,
|
||||||
|
dataList: list,
|
||||||
|
deletedIds: opts?.deletedIds || []
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const header = useMemo(() => {
|
const {
|
||||||
return {
|
updateChunkedList: updateInstanceChunkedList,
|
||||||
title: (
|
cacheDataListRef: cacheInsDataListRef
|
||||||
<div className="flex items-center">
|
} = useUpdateChunkedList({
|
||||||
<span className="font-600">
|
dataList: modelInstances,
|
||||||
{intl.formatMessage({ id: 'menu.models.deployment' })}
|
limit: 100,
|
||||||
</span>
|
setDataList: setModelInstances
|
||||||
<Segmented
|
});
|
||||||
options={[
|
|
||||||
{
|
const getAllModelInstances = useMemoizedFn(async () => {
|
||||||
label: 'Model View',
|
try {
|
||||||
value: TabsValueMap.ModelView,
|
instancesToken.current?.cancel?.();
|
||||||
icon: <IconFont type={'icon-models'}></IconFont>
|
instancesToken.current = createAxiosToken();
|
||||||
},
|
const params = {
|
||||||
{
|
page: -1
|
||||||
label: 'Instance View',
|
};
|
||||||
value: TabsValueMap.InstanceView,
|
const res: any = await queryModelsInstances(params, {
|
||||||
icon: <IconFont type={'icon-database'}></IconFont>
|
token: instancesToken.current.token
|
||||||
}
|
});
|
||||||
]}
|
cacheInsDataListRef.current = res.items || [];
|
||||||
size="middle"
|
setModelInstances(res.items || []);
|
||||||
className="m-l-24 font-600"
|
} catch (error) {
|
||||||
value={activeKey}
|
// ignore
|
||||||
onChange={(key) => setActiveKey(key)}
|
}
|
||||||
></Segmented>
|
});
|
||||||
</div>
|
|
||||||
)
|
const fetchData = useMemoizedFn(
|
||||||
|
async (params?: {
|
||||||
|
loadingVal?: boolean;
|
||||||
|
query?: {
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
search: string;
|
||||||
|
categories: any[];
|
||||||
|
sort_by: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
const { loadingVal, query } = params || {};
|
||||||
|
axiosToken?.cancel?.();
|
||||||
|
axiosToken = createAxiosToken();
|
||||||
|
setDataSource((pre) => {
|
||||||
|
pre.loading = loadingVal ?? true;
|
||||||
|
return { ...pre };
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
..._.pickBy(query || queryParams, (val: any) => !!val)
|
||||||
|
};
|
||||||
|
const res: any = await queryModelsList(params, {
|
||||||
|
cancelToken: axiosToken.token
|
||||||
|
});
|
||||||
|
|
||||||
|
// if the current page is beyond total page, fetch again
|
||||||
|
if (
|
||||||
|
!res.items.length &&
|
||||||
|
params.page > res.pagination.totalPage &&
|
||||||
|
res.pagination.totalPage > 0
|
||||||
|
) {
|
||||||
|
const newParams = {
|
||||||
|
...params,
|
||||||
|
page: res.pagination.totalPage
|
||||||
|
};
|
||||||
|
const newRes: any = await queryModelsList(newParams, {
|
||||||
|
cancelToken: axiosToken.token
|
||||||
|
});
|
||||||
|
setDataSource({
|
||||||
|
dataList: newRes.items || [],
|
||||||
|
loading: false,
|
||||||
|
loadend: true,
|
||||||
|
total: newRes.pagination.total,
|
||||||
|
deletedIds: []
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDataSource({
|
||||||
|
dataList: res.items || [],
|
||||||
|
loading: false,
|
||||||
|
loadend: true,
|
||||||
|
total: res.pagination.total,
|
||||||
|
deletedIds: []
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (!isPageHidden.current) {
|
||||||
|
setDataSource({
|
||||||
|
dataList: [],
|
||||||
|
loading: false,
|
||||||
|
loadend: true,
|
||||||
|
total: dataSource.total,
|
||||||
|
deletedIds: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleQueryChange = (params: any) => {
|
||||||
|
setQueryParams({
|
||||||
|
...queryParams,
|
||||||
|
...params
|
||||||
|
});
|
||||||
|
fetchData({ query: { ...queryParams, ...params } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = useMemoizedFn(
|
||||||
|
(page: number, pageSize: number | undefined) => {
|
||||||
|
console.log('page change', page, pageSize);
|
||||||
|
handleQueryChange({
|
||||||
|
page: page,
|
||||||
|
perPage: pageSize || 10
|
||||||
|
});
|
||||||
|
setPagination({
|
||||||
|
perPage: pageSize || 10
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateHandler = (list: any) => {
|
||||||
|
_.each(list, (data: any) => {
|
||||||
|
updateChunkedList(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateInstanceHandler = (list: any) => {
|
||||||
|
// filter the data
|
||||||
|
_.each(list, (data: any) => {
|
||||||
|
updateInstanceChunkedList(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createModelsChunkRequest = useMemoizedFn(
|
||||||
|
async (params?: {
|
||||||
|
search: string;
|
||||||
|
categories: any[];
|
||||||
|
cluster_id: number;
|
||||||
|
state?: string;
|
||||||
|
}) => {
|
||||||
|
const search = params?.search || queryParams.search;
|
||||||
|
const categories = params?.categories || queryParams.categories;
|
||||||
|
chunkRequedtRef.current?.current?.cancel?.();
|
||||||
|
try {
|
||||||
|
const query = {
|
||||||
|
search: search,
|
||||||
|
categories: categories
|
||||||
|
};
|
||||||
|
chunkRequedtRef.current = setChunkRequest({
|
||||||
|
url: `${MODELS_API}?${qs.stringify(_.pickBy(query, (val: any) => !!val))}`,
|
||||||
|
handler: updateHandler
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const createModelsInstanceChunkRequest = useMemoizedFn(async () => {
|
||||||
|
chunkInstanceRequedtRef.current?.current?.cancel?.();
|
||||||
|
cacheInsDataListRef.current = [];
|
||||||
|
try {
|
||||||
|
chunkInstanceRequedtRef.current = setModelInstanceChunkRequest({
|
||||||
|
url: `${MODEL_INSTANCE_API}`,
|
||||||
|
params: {},
|
||||||
|
handler: updateInstanceHandler
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancelRequestsOnPageInactive = useMemoizedFn(() => {
|
||||||
|
isPageHidden.current = true;
|
||||||
|
chunkRequedtRef.current?.current?.cancel?.();
|
||||||
|
cacheDataListRef.current = [];
|
||||||
|
cacheInsDataListRef.current = [];
|
||||||
|
chunkInstanceRequedtRef.current?.current?.cancel?.();
|
||||||
|
instancesToken.current?.cancel?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
const resumeRequestsOnPageActive = useMemoizedFn(async () => {
|
||||||
|
isPageHidden.current = false;
|
||||||
|
fetchData({
|
||||||
|
loadingVal: false
|
||||||
|
});
|
||||||
|
await getAllModelInstances();
|
||||||
|
await createModelsInstanceChunkRequest();
|
||||||
|
await createModelsChunkRequest();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOnCancelViewLogs = useMemoizedFn(async () => {
|
||||||
|
resumeRequestsOnPageActive();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSearchBySilent = useMemoizedFn(async () => {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 300);
|
||||||
|
});
|
||||||
|
fetchData({
|
||||||
|
loadingVal: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSearch = useMemoizedFn(async (params?: any) => {
|
||||||
|
await fetchData(params);
|
||||||
|
});
|
||||||
|
|
||||||
|
const debounceUpdateFilter = _.debounce((e: any) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
search: e.target.value
|
||||||
|
});
|
||||||
|
createModelsChunkRequest({
|
||||||
|
search: e.target.value,
|
||||||
|
categories: queryParams.categories,
|
||||||
|
cluster_id: queryParams.cluster_id || 0
|
||||||
|
});
|
||||||
|
}, 350);
|
||||||
|
|
||||||
|
const handleNameChange = useMemoizedFn(debounceUpdateFilter);
|
||||||
|
|
||||||
|
const handleCategoryChange = async (value: any) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
categories: value
|
||||||
|
});
|
||||||
|
createModelsChunkRequest({
|
||||||
|
search: queryParams.search,
|
||||||
|
cluster_id: queryParams.cluster_id || 0,
|
||||||
|
categories: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnStatusChange = async (value: string | undefined) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
state: value
|
||||||
|
});
|
||||||
|
createModelsChunkRequest({
|
||||||
|
search: queryParams.search,
|
||||||
|
categories: queryParams.categories,
|
||||||
|
cluster_id: queryParams.cluster_id || 0,
|
||||||
|
state: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClusterChange = async (value: number) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
cluster_id: value
|
||||||
|
});
|
||||||
|
createModelsChunkRequest({
|
||||||
|
search: queryParams.search,
|
||||||
|
categories: queryParams.categories,
|
||||||
|
cluster_id: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnSortChange = (order: TableOrder | Array<TableOrder>) => {
|
||||||
|
let orderList = Array.isArray(order) ? order : [order];
|
||||||
|
if (orderList[0].columnKey === 'replicas') {
|
||||||
|
orderList.push({
|
||||||
|
columnKey: 'ready_replicas',
|
||||||
|
order: orderList[0].order
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const sortKeys = handleMultiSortChange(orderList);
|
||||||
|
setQueryParams((pre: any) => {
|
||||||
|
return {
|
||||||
|
...pre,
|
||||||
|
page: 1,
|
||||||
|
sort_by: sortKeys.join(',')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
fetchData({
|
||||||
|
query: {
|
||||||
|
...queryParams,
|
||||||
|
page: 1,
|
||||||
|
sort_by: sortKeys.join(',')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnFilterChange = (filters: any) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
...filters
|
||||||
|
});
|
||||||
|
createModelsChunkRequest({
|
||||||
|
search: queryParams.search,
|
||||||
|
...filters
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
const getTableData = async (loadingVal?: boolean) => {
|
||||||
|
axiosToken?.cancel?.();
|
||||||
|
axiosToken = createAxiosToken();
|
||||||
|
setDataSource((pre) => {
|
||||||
|
pre.loading = loadingVal ?? true;
|
||||||
|
return { ...pre };
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
..._.pickBy(queryParams, (val: any) => !!val)
|
||||||
|
};
|
||||||
|
const res: any = await queryModelsList(params, {
|
||||||
|
cancelToken: axiosToken.token
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [activeKey, intl]);
|
|
||||||
|
const init = async () => {
|
||||||
|
const [modelRes] = await Promise.all([getTableData()]);
|
||||||
|
|
||||||
|
setDataSource({
|
||||||
|
dataList: modelRes.items || [],
|
||||||
|
loading: false,
|
||||||
|
loadend: true,
|
||||||
|
total: modelRes.pagination?.total || 0,
|
||||||
|
deletedIds: []
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
createModelsInstanceChunkRequest();
|
||||||
|
createModelsChunkRequest();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
init();
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
axiosToken?.cancel?.();
|
||||||
|
cancelRequestsOnPageInactive();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setDisableExpand = useMemoizedFn((record: any) => {
|
||||||
|
return !record?.replicas;
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
cancelRequestsOnPageInactive,
|
||||||
|
resumeRequestsOnPageActive
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainerInner header={header}>
|
<TableContext.Provider
|
||||||
<Tabs
|
value={{
|
||||||
renderTabBar={() => <></>}
|
allChildren: modelInstances,
|
||||||
items={[
|
setDisableExpand: setDisableExpand
|
||||||
{
|
}}
|
||||||
key: TabsValueMap.ModelView,
|
>
|
||||||
label: 'Model View',
|
<TableList
|
||||||
children: <ModelView></ModelView>
|
dataSource={dataSource.dataList}
|
||||||
},
|
onStatusChange={handleOnStatusChange}
|
||||||
{
|
handleNameChange={handleNameChange}
|
||||||
key: TabsValueMap.InstanceView,
|
handleCategoryChange={handleCategoryChange}
|
||||||
label: 'Instance View',
|
handleClusterChange={handleClusterChange}
|
||||||
children: <InstanceView></InstanceView>
|
handleSearch={handleSearch}
|
||||||
}
|
handlePageChange={handlePageChange}
|
||||||
]}
|
handleDeleteSuccess={fetchData}
|
||||||
activeKey={activeKey}
|
handleOnToggleExpandAll={createModelsInstanceChunkRequest}
|
||||||
></Tabs>
|
onViewLogs={cancelRequestsOnPageInactive}
|
||||||
</PageContainerInner>
|
onCancelViewLogs={handleOnCancelViewLogs}
|
||||||
|
onStop={handleSearchBySilent}
|
||||||
|
onStart={handleSearchBySilent}
|
||||||
|
onTableSort={handleOnSortChange}
|
||||||
|
onFilterChange={handleOnFilterChange}
|
||||||
|
onDeleteInstanceFromCache={handleDeleteInstanceFromCache}
|
||||||
|
sortOrder={sortOrder}
|
||||||
|
queryParams={queryParams}
|
||||||
|
loading={dataSource.loading}
|
||||||
|
loadend={dataSource.loadend}
|
||||||
|
total={dataSource.total}
|
||||||
|
deleteIds={dataSource.deletedIds}
|
||||||
|
></TableList>
|
||||||
|
</TableContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default Playground;
|
export default Models;
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ const Dot = ({ color }: { color: string }) => {
|
|||||||
|
|
||||||
const useFilterStatus = (options?: {
|
const useFilterStatus = (options?: {
|
||||||
onStatusChange?: (value?: any) => void;
|
onStatusChange?: (value?: any) => void;
|
||||||
|
optionList?: (Global.BaseOption<string> & { color: string })[];
|
||||||
}) => {
|
}) => {
|
||||||
const { onStatusChange } = options || {};
|
const { onStatusChange, optionList } = options || {};
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
@@ -45,8 +46,10 @@ const useFilterStatus = (options?: {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mergedOptions = optionList || statusOptions;
|
||||||
|
|
||||||
const labelRender = (item: any) => {
|
const labelRender = (item: any) => {
|
||||||
const current = statusOptions.find((option) => option.value === item.value);
|
const current = mergedOptions.find((option) => option.value === item.value);
|
||||||
return (
|
return (
|
||||||
<span className="flex-center gap-8">
|
<span className="flex-center gap-8">
|
||||||
{current && <Dot color={current.color}></Dot>}
|
{current && <Dot color={current.color}></Dot>}
|
||||||
@@ -69,7 +72,7 @@ const useFilterStatus = (options?: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusOptions,
|
statusOptions: mergedOptions,
|
||||||
labelRender,
|
labelRender,
|
||||||
optionRender,
|
optionRender,
|
||||||
handleStatusChange
|
handleStatusChange
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { PageSize } from '@/components/logs-viewer/config';
|
||||||
|
import useBodyScroll from '@/hooks/use-body-scroll';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { MODEL_INSTANCE_API } from '../apis';
|
||||||
|
import { InstanceRealtimeLogStatus } from '../config';
|
||||||
|
import { ModelInstanceListItem as ListItem } from '../config/types';
|
||||||
|
|
||||||
|
const useViewInstanceLogs = () => {
|
||||||
|
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
|
||||||
|
const [openModalStatus, setOpenModalStatus] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
currentData: {
|
||||||
|
url: string;
|
||||||
|
status: string;
|
||||||
|
id?: number | string;
|
||||||
|
modelId?: number | string;
|
||||||
|
tail?: number;
|
||||||
|
};
|
||||||
|
}>({
|
||||||
|
open: false,
|
||||||
|
currentData: {
|
||||||
|
url: '',
|
||||||
|
status: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const openModal = (row: ListItem) => {
|
||||||
|
setOpenModalStatus({
|
||||||
|
open: true,
|
||||||
|
currentData: {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
saveScrollHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setOpenModalStatus({
|
||||||
|
open: false,
|
||||||
|
currentData: {
|
||||||
|
url: '',
|
||||||
|
status: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
restoreScrollHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
openViewLogsModalStatus: openModalStatus,
|
||||||
|
setOpenViewLogsModalStatus: setOpenModalStatus,
|
||||||
|
openViewLogsModal: openModal,
|
||||||
|
closeViewLogsModal: closeModal
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useViewInstanceLogs;
|
||||||
+107
-460
@@ -1,445 +1,97 @@
|
|||||||
import TableContext from '@/components/seal-table/table-context';
|
import IconFont from '@/components/icon-font';
|
||||||
import { TableOrder } from '@/components/seal-table/types';
|
import { PageContainerInner } from '@/pages/_components/page-box';
|
||||||
import { PaginationKey } from '@/config/settings';
|
import { useIntl } from '@umijs/max';
|
||||||
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
|
||||||
import { usePaginationStatus } from '@/hooks/use-pagination-status';
|
|
||||||
import { useTableMultiSort } from '@/hooks/use-table-sort';
|
|
||||||
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
|
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import _ from 'lodash';
|
import { Segmented, Tabs } from 'antd';
|
||||||
import qs from 'query-string';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { DeploymentsContext } from './config/deploments-context';
|
||||||
import {
|
import ModelView from './deployments';
|
||||||
MODELS_API,
|
import useFormInitialValues from './hooks/use-form-initial-values';
|
||||||
MODEL_INSTANCE_API,
|
import InstanceView from './instance-view';
|
||||||
queryModelsInstances,
|
|
||||||
queryModelsList
|
|
||||||
} from './apis';
|
|
||||||
import TableList from './components/table-list';
|
|
||||||
import { ListItem } from './config/types';
|
|
||||||
|
|
||||||
const Models = () => {
|
const TabsValueMap = {
|
||||||
const { pagination, setPagination } = usePaginationStatus(
|
ModelView: 'modelView',
|
||||||
PaginationKey.Deployments
|
InstanceView: 'instanceView'
|
||||||
);
|
};
|
||||||
const { sortOrder, handleMultiSortChange } = useTableMultiSort();
|
|
||||||
const { setChunkRequest, createAxiosToken } = useSetChunkRequest();
|
|
||||||
const { setChunkRequest: setModelInstanceChunkRequest } =
|
|
||||||
useSetChunkRequest();
|
|
||||||
const [modelInstances, setModelInstances] = useState<any[]>([]);
|
|
||||||
const [dataSource, setDataSource] = useState<{
|
|
||||||
dataList: ListItem[];
|
|
||||||
deletedIds: number[];
|
|
||||||
loading: boolean;
|
|
||||||
loadend: boolean;
|
|
||||||
total: number;
|
|
||||||
}>({
|
|
||||||
dataList: [],
|
|
||||||
deletedIds: [],
|
|
||||||
loading: false,
|
|
||||||
loadend: false,
|
|
||||||
total: 0
|
|
||||||
});
|
|
||||||
const chunkRequedtRef = useRef<any>();
|
|
||||||
const chunkInstanceRequedtRef = useRef<any>();
|
|
||||||
const isPageHidden = useRef(false);
|
|
||||||
const instancesToken = useRef<any>();
|
|
||||||
let axiosToken = createAxiosToken();
|
|
||||||
const [queryParams, setQueryParams] = useState({
|
|
||||||
page: 1,
|
|
||||||
perPage: 10,
|
|
||||||
search: '',
|
|
||||||
cluster_id: 0,
|
|
||||||
categories: [],
|
|
||||||
state: '',
|
|
||||||
sort_by: '',
|
|
||||||
...pagination
|
|
||||||
});
|
|
||||||
|
|
||||||
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
|
||||||
events: ['UPDATE'],
|
|
||||||
dataList: dataSource.dataList,
|
|
||||||
setDataList(list, opts?: any) {
|
|
||||||
setDataSource((pre) => {
|
|
||||||
return {
|
|
||||||
total: pre.total,
|
|
||||||
loading: false,
|
|
||||||
loadend: true,
|
|
||||||
dataList: list,
|
|
||||||
deletedIds: opts?.deletedIds || []
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const LLModels: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
const {
|
const {
|
||||||
updateChunkedList: updateInstanceChunkedList,
|
generateFormValues,
|
||||||
cacheDataListRef: cacheInsDataListRef
|
clusterList,
|
||||||
} = useUpdateChunkedList({
|
getClusterList,
|
||||||
dataList: modelInstances,
|
getWorkerList,
|
||||||
limit: 100,
|
workerList
|
||||||
setDataList: setModelInstances
|
} = useFormInitialValues();
|
||||||
});
|
const [activeKey, setActiveKey] = useState(TabsValueMap.ModelView);
|
||||||
|
const modelViewRef = useRef<any>(null);
|
||||||
const getAllModelInstances = useMemoizedFn(async () => {
|
const instanceViewRef = useRef<any>(null);
|
||||||
try {
|
|
||||||
instancesToken.current?.cancel?.();
|
|
||||||
instancesToken.current = createAxiosToken();
|
|
||||||
const params = {
|
|
||||||
page: -1
|
|
||||||
};
|
|
||||||
const res: any = await queryModelsInstances(params, {
|
|
||||||
token: instancesToken.current.token
|
|
||||||
});
|
|
||||||
cacheInsDataListRef.current = res.items || [];
|
|
||||||
setModelInstances(res.items || []);
|
|
||||||
} catch (error) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchData = useMemoizedFn(
|
|
||||||
async (params?: {
|
|
||||||
loadingVal?: boolean;
|
|
||||||
query?: {
|
|
||||||
page: number;
|
|
||||||
perPage: number;
|
|
||||||
search: string;
|
|
||||||
categories: any[];
|
|
||||||
sort_by: string;
|
|
||||||
};
|
|
||||||
}) => {
|
|
||||||
const { loadingVal, query } = params || {};
|
|
||||||
axiosToken?.cancel?.();
|
|
||||||
axiosToken = createAxiosToken();
|
|
||||||
setDataSource((pre) => {
|
|
||||||
pre.loading = loadingVal ?? true;
|
|
||||||
return { ...pre };
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
..._.pickBy(query || queryParams, (val: any) => !!val)
|
|
||||||
};
|
|
||||||
const res: any = await queryModelsList(params, {
|
|
||||||
cancelToken: axiosToken.token
|
|
||||||
});
|
|
||||||
|
|
||||||
// if the current page is beyond total page, fetch again
|
|
||||||
if (
|
|
||||||
!res.items.length &&
|
|
||||||
params.page > res.pagination.totalPage &&
|
|
||||||
res.pagination.totalPage > 0
|
|
||||||
) {
|
|
||||||
const newParams = {
|
|
||||||
...params,
|
|
||||||
page: res.pagination.totalPage
|
|
||||||
};
|
|
||||||
const newRes: any = await queryModelsList(newParams, {
|
|
||||||
cancelToken: axiosToken.token
|
|
||||||
});
|
|
||||||
setDataSource({
|
|
||||||
dataList: newRes.items || [],
|
|
||||||
loading: false,
|
|
||||||
loadend: true,
|
|
||||||
total: newRes.pagination.total,
|
|
||||||
deletedIds: []
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDataSource({
|
|
||||||
dataList: res.items || [],
|
|
||||||
loading: false,
|
|
||||||
loadend: true,
|
|
||||||
total: res.pagination.total,
|
|
||||||
deletedIds: []
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (!isPageHidden.current) {
|
|
||||||
setDataSource({
|
|
||||||
dataList: [],
|
|
||||||
loading: false,
|
|
||||||
loadend: true,
|
|
||||||
total: dataSource.total,
|
|
||||||
deletedIds: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleQueryChange = (params: any) => {
|
|
||||||
setQueryParams({
|
|
||||||
...queryParams,
|
|
||||||
...params
|
|
||||||
});
|
|
||||||
fetchData({ query: { ...queryParams, ...params } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePageChange = useMemoizedFn(
|
|
||||||
(page: number, pageSize: number | undefined) => {
|
|
||||||
console.log('page change', page, pageSize);
|
|
||||||
handleQueryChange({
|
|
||||||
page: page,
|
|
||||||
perPage: pageSize || 10
|
|
||||||
});
|
|
||||||
setPagination({
|
|
||||||
perPage: pageSize || 10
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateHandler = (list: any) => {
|
|
||||||
_.each(list, (data: any) => {
|
|
||||||
updateChunkedList(data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateInstanceHandler = (list: any) => {
|
|
||||||
// filter the data
|
|
||||||
_.each(list, (data: any) => {
|
|
||||||
updateInstanceChunkedList(data);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const createModelsChunkRequest = useMemoizedFn(
|
|
||||||
async (params?: {
|
|
||||||
search: string;
|
|
||||||
categories: any[];
|
|
||||||
cluster_id: number;
|
|
||||||
state?: string;
|
|
||||||
}) => {
|
|
||||||
const search = params?.search || queryParams.search;
|
|
||||||
const categories = params?.categories || queryParams.categories;
|
|
||||||
chunkRequedtRef.current?.current?.cancel?.();
|
|
||||||
try {
|
|
||||||
const query = {
|
|
||||||
search: search,
|
|
||||||
categories: categories
|
|
||||||
};
|
|
||||||
chunkRequedtRef.current = setChunkRequest({
|
|
||||||
url: `${MODELS_API}?${qs.stringify(_.pickBy(query, (val: any) => !!val))}`,
|
|
||||||
handler: updateHandler
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const createModelsInstanceChunkRequest = useMemoizedFn(async () => {
|
|
||||||
chunkInstanceRequedtRef.current?.current?.cancel?.();
|
|
||||||
cacheInsDataListRef.current = [];
|
|
||||||
try {
|
|
||||||
chunkInstanceRequedtRef.current = setModelInstanceChunkRequest({
|
|
||||||
url: `${MODEL_INSTANCE_API}`,
|
|
||||||
params: {},
|
|
||||||
handler: updateInstanceHandler
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleOnViewLogs = useMemoizedFn(() => {
|
|
||||||
isPageHidden.current = true;
|
|
||||||
chunkRequedtRef.current?.current?.cancel?.();
|
|
||||||
cacheDataListRef.current = [];
|
|
||||||
cacheInsDataListRef.current = [];
|
|
||||||
chunkInstanceRequedtRef.current?.current?.cancel?.();
|
|
||||||
instancesToken.current?.cancel?.();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleOnCancelViewLogs = useMemoizedFn(async () => {
|
|
||||||
isPageHidden.current = false;
|
|
||||||
fetchData({
|
|
||||||
loadingVal: false
|
|
||||||
});
|
|
||||||
await getAllModelInstances();
|
|
||||||
await createModelsInstanceChunkRequest();
|
|
||||||
await createModelsChunkRequest();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSearchBySilent = useMemoizedFn(async () => {
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 300);
|
|
||||||
});
|
|
||||||
fetchData({
|
|
||||||
loadingVal: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSearch = useMemoizedFn(async (params?: any) => {
|
|
||||||
await fetchData(params);
|
|
||||||
});
|
|
||||||
|
|
||||||
const debounceUpdateFilter = _.debounce((e: any) => {
|
|
||||||
handleQueryChange({
|
|
||||||
page: 1,
|
|
||||||
search: e.target.value
|
|
||||||
});
|
|
||||||
createModelsChunkRequest({
|
|
||||||
search: e.target.value,
|
|
||||||
categories: queryParams.categories,
|
|
||||||
cluster_id: queryParams.cluster_id || 0
|
|
||||||
});
|
|
||||||
}, 350);
|
|
||||||
|
|
||||||
const handleNameChange = useMemoizedFn(debounceUpdateFilter);
|
|
||||||
|
|
||||||
const handleCategoryChange = async (value: any) => {
|
|
||||||
handleQueryChange({
|
|
||||||
page: 1,
|
|
||||||
categories: value
|
|
||||||
});
|
|
||||||
createModelsChunkRequest({
|
|
||||||
search: queryParams.search,
|
|
||||||
cluster_id: queryParams.cluster_id || 0,
|
|
||||||
categories: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnStatusChange = async (value: string | undefined) => {
|
|
||||||
handleQueryChange({
|
|
||||||
page: 1,
|
|
||||||
state: value
|
|
||||||
});
|
|
||||||
createModelsChunkRequest({
|
|
||||||
search: queryParams.search,
|
|
||||||
categories: queryParams.categories,
|
|
||||||
cluster_id: queryParams.cluster_id || 0,
|
|
||||||
state: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClusterChange = async (value: number) => {
|
|
||||||
handleQueryChange({
|
|
||||||
page: 1,
|
|
||||||
cluster_id: value
|
|
||||||
});
|
|
||||||
createModelsChunkRequest({
|
|
||||||
search: queryParams.search,
|
|
||||||
categories: queryParams.categories,
|
|
||||||
cluster_id: value
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnSortChange = (order: TableOrder | Array<TableOrder>) => {
|
|
||||||
let orderList = Array.isArray(order) ? order : [order];
|
|
||||||
if (orderList[0].columnKey === 'replicas') {
|
|
||||||
orderList.push({
|
|
||||||
columnKey: 'ready_replicas',
|
|
||||||
order: orderList[0].order
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const sortKeys = handleMultiSortChange(orderList);
|
|
||||||
setQueryParams((pre: any) => {
|
|
||||||
return {
|
|
||||||
...pre,
|
|
||||||
page: 1,
|
|
||||||
sort_by: sortKeys.join(',')
|
|
||||||
};
|
|
||||||
});
|
|
||||||
fetchData({
|
|
||||||
query: {
|
|
||||||
...queryParams,
|
|
||||||
page: 1,
|
|
||||||
sort_by: sortKeys.join(',')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnFilterChange = (filters: any) => {
|
|
||||||
handleQueryChange({
|
|
||||||
page: 1,
|
|
||||||
...filters
|
|
||||||
});
|
|
||||||
createModelsChunkRequest({
|
|
||||||
search: queryParams.search,
|
|
||||||
...filters
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteInstanceFromCache = (id: number) => {
|
|
||||||
cacheInsDataListRef.current = cacheInsDataListRef.current.filter(
|
|
||||||
(item) => item.id !== id
|
|
||||||
);
|
|
||||||
setModelInstances(cacheInsDataListRef.current);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer: any = null;
|
const getData = async () => {
|
||||||
// fetch data first time
|
await Promise.all([getClusterList(), getWorkerList()]);
|
||||||
const getTableData = async (loadingVal?: boolean) => {
|
|
||||||
axiosToken?.cancel?.();
|
|
||||||
axiosToken = createAxiosToken();
|
|
||||||
setDataSource((pre) => {
|
|
||||||
pre.loading = loadingVal ?? true;
|
|
||||||
return { ...pre };
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
..._.pickBy(queryParams, (val: any) => !!val)
|
|
||||||
};
|
|
||||||
const res: any = await queryModelsList(params, {
|
|
||||||
cancelToken: axiosToken.token
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
} catch (error) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const init = async () => {
|
|
||||||
const [modelRes] = await Promise.all([getTableData()]);
|
|
||||||
|
|
||||||
setDataSource({
|
|
||||||
dataList: modelRes.items || [],
|
|
||||||
loading: false,
|
|
||||||
loadend: true,
|
|
||||||
total: modelRes.pagination?.total || 0,
|
|
||||||
deletedIds: []
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timer);
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
createModelsInstanceChunkRequest();
|
|
||||||
createModelsChunkRequest();
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
init();
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
axiosToken?.cancel?.();
|
|
||||||
chunkRequedtRef.current?.current?.cancel?.();
|
|
||||||
cacheDataListRef.current = [];
|
|
||||||
cacheInsDataListRef.current = [];
|
|
||||||
chunkInstanceRequedtRef.current?.current?.cancel?.();
|
|
||||||
instancesToken.current?.cancel?.();
|
|
||||||
};
|
};
|
||||||
|
getData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setDisableExpand = useMemoizedFn((record: any) => {
|
const handleTabChange = useMemoizedFn((key: string) => {
|
||||||
return !record?.replicas;
|
if (key === TabsValueMap.ModelView) {
|
||||||
|
modelViewRef.current?.resumeRequestsOnPageActive();
|
||||||
|
instanceViewRef.current?.cancelRequestsOnPageInactive();
|
||||||
|
}
|
||||||
|
if (key === TabsValueMap.InstanceView) {
|
||||||
|
instanceViewRef.current?.resumeRequestsOnPageActive();
|
||||||
|
modelViewRef.current?.cancelRequestsOnPageInactive();
|
||||||
|
}
|
||||||
|
setActiveKey(key);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const header = useMemo(() => {
|
||||||
|
return {
|
||||||
|
title: (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-600">
|
||||||
|
{intl.formatMessage({ id: 'menu.models.deployment' })}
|
||||||
|
</span>
|
||||||
|
<Segmented
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({ id: 'models.table.modelView' }),
|
||||||
|
value: TabsValueMap.ModelView,
|
||||||
|
icon: <IconFont type={'icon-models'}></IconFont>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({ id: 'models.table.instanceView' }),
|
||||||
|
value: TabsValueMap.InstanceView,
|
||||||
|
icon: <IconFont type={'icon-instance-outline'}></IconFont>
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
size="middle"
|
||||||
|
className="m-l-24 font-600"
|
||||||
|
value={activeKey}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
></Segmented>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}, [activeKey, intl]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleVisibilityChange = async () => {
|
const handleVisibilityChange = async () => {
|
||||||
if (document.visibilityState === 'visible') {
|
if (document.visibilityState === 'visible') {
|
||||||
isPageHidden.current = false;
|
if (activeKey === TabsValueMap.ModelView) {
|
||||||
fetchData({
|
modelViewRef.current?.resumeRequestsOnPageActive();
|
||||||
loadingVal: false
|
}
|
||||||
});
|
if (activeKey === TabsValueMap.InstanceView) {
|
||||||
await getAllModelInstances();
|
instanceViewRef.current?.resumeRequestsOnPageActive();
|
||||||
await createModelsInstanceChunkRequest();
|
}
|
||||||
await createModelsChunkRequest();
|
|
||||||
} else {
|
} else {
|
||||||
isPageHidden.current = true;
|
if (activeKey === TabsValueMap.ModelView) {
|
||||||
chunkRequedtRef.current?.current?.cancel?.();
|
modelViewRef.current?.cancelRequestsOnPageInactive();
|
||||||
cacheDataListRef.current = [];
|
}
|
||||||
cacheInsDataListRef.current = [];
|
if (activeKey === TabsValueMap.InstanceView) {
|
||||||
chunkInstanceRequedtRef.current?.current?.cancel?.();
|
instanceViewRef.current?.cancelRequestsOnPageInactive();
|
||||||
instancesToken.current?.cancel?.();
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -448,41 +100,36 @@ const Models = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
};
|
};
|
||||||
}, [fetchData, createModelsChunkRequest, createModelsInstanceChunkRequest]);
|
}, [activeKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContext.Provider
|
<PageContainerInner header={header}>
|
||||||
value={{
|
<DeploymentsContext.Provider
|
||||||
allChildren: modelInstances,
|
value={{
|
||||||
setDisableExpand: setDisableExpand
|
generateFormValues,
|
||||||
}}
|
clusterList,
|
||||||
>
|
workerList
|
||||||
<TableList
|
}}
|
||||||
dataSource={dataSource.dataList}
|
>
|
||||||
onStatusChange={handleOnStatusChange}
|
<Tabs
|
||||||
handleNameChange={handleNameChange}
|
renderTabBar={() => <></>}
|
||||||
handleCategoryChange={handleCategoryChange}
|
items={[
|
||||||
handleClusterChange={handleClusterChange}
|
{
|
||||||
handleSearch={handleSearch}
|
key: TabsValueMap.ModelView,
|
||||||
handlePageChange={handlePageChange}
|
label: intl.formatMessage({ id: 'models.table.modelView' }),
|
||||||
handleDeleteSuccess={fetchData}
|
children: <ModelView ref={modelViewRef}></ModelView>
|
||||||
handleOnToggleExpandAll={createModelsInstanceChunkRequest}
|
},
|
||||||
onViewLogs={handleOnViewLogs}
|
{
|
||||||
onCancelViewLogs={handleOnCancelViewLogs}
|
key: TabsValueMap.InstanceView,
|
||||||
onStop={handleSearchBySilent}
|
label: intl.formatMessage({ id: 'models.table.instanceView' }),
|
||||||
onStart={handleSearchBySilent}
|
children: <InstanceView ref={instanceViewRef}></InstanceView>
|
||||||
onTableSort={handleOnSortChange}
|
}
|
||||||
onFilterChange={handleOnFilterChange}
|
]}
|
||||||
onDeleteInstanceFromCache={handleDeleteInstanceFromCache}
|
activeKey={activeKey}
|
||||||
sortOrder={sortOrder}
|
></Tabs>
|
||||||
queryParams={queryParams}
|
</DeploymentsContext.Provider>
|
||||||
loading={dataSource.loading}
|
</PageContainerInner>
|
||||||
loadend={dataSource.loadend}
|
|
||||||
total={dataSource.total}
|
|
||||||
deleteIds={dataSource.deletedIds}
|
|
||||||
></TableList>
|
|
||||||
</TableContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Models;
|
export default LLModels;
|
||||||
|
|||||||
@@ -9,38 +9,71 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { ConfigProvider, Table } from 'antd';
|
import { ConfigProvider, Table } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { forwardRef, useImperativeHandle } from 'react';
|
||||||
import {
|
import {
|
||||||
deleteModelInstance,
|
deleteModelInstance,
|
||||||
MODEL_INSTANCE_API,
|
MODEL_INSTANCE_API,
|
||||||
queryModelsInstances
|
queryModelsInstances
|
||||||
} from '../apis';
|
} from '../apis';
|
||||||
|
import ViewLogsModal from '../components/view-logs-modal';
|
||||||
|
import { useDeploymentsContext } from '../config/deploments-context';
|
||||||
import { ModelInstanceListItem as ListItem } from '../config/types';
|
import { ModelInstanceListItem as ListItem } from '../config/types';
|
||||||
|
import useViewInstanceLogs from '../hooks/use-view-instance-logs';
|
||||||
|
import LeftFilters from './left-filters';
|
||||||
import useInstanceColumns from './use-instance-columns';
|
import useInstanceColumns from './use-instance-columns';
|
||||||
|
|
||||||
const InstanceView: React.FC = () => {
|
const InstanceView = forwardRef((props, ref) => {
|
||||||
const {
|
const {
|
||||||
dataSource,
|
dataSource,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
queryParams,
|
queryParams,
|
||||||
modalRef,
|
modalRef,
|
||||||
handleTableChange,
|
handleTableChange,
|
||||||
|
cancelChunkRequest,
|
||||||
|
createTableListChunkRequest,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
handleDeleteBatch,
|
handleDeleteBatch,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
handleQueryChange,
|
||||||
handlePageChange,
|
handlePageChange,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
handleNameChange
|
handleNameChange
|
||||||
} = useTableFetch<ListItem>({
|
} = useTableFetch<ListItem>({
|
||||||
key: PaginationKey.Providers,
|
key: PaginationKey.Instances,
|
||||||
fetchAPI: queryModelsInstances,
|
fetchAPI: queryModelsInstances,
|
||||||
deleteAPI: deleteModelInstance,
|
deleteAPI: deleteModelInstance,
|
||||||
watch: false,
|
watch: true,
|
||||||
API: MODEL_INSTANCE_API,
|
API: MODEL_INSTANCE_API,
|
||||||
contentForDelete: 'menu.models.providers'
|
contentForDelete: 'menu.models.instances'
|
||||||
});
|
});
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { clusterList, workerList } = useDeploymentsContext();
|
||||||
|
const { openViewLogsModal, openViewLogsModalStatus, closeViewLogsModal } =
|
||||||
|
useViewInstanceLogs();
|
||||||
|
|
||||||
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {});
|
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {
|
||||||
|
if (val === 'delete') {
|
||||||
|
handleDelete(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val === 'viewlog') {
|
||||||
|
openViewLogsModal(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancelRequestsOnPageInactive = useMemoizedFn(() => {
|
||||||
|
cancelChunkRequest();
|
||||||
|
});
|
||||||
|
|
||||||
|
const resumeRequestsOnPageActive = useMemoizedFn(() => {
|
||||||
|
fetchData({} as any, true);
|
||||||
|
createTableListChunkRequest();
|
||||||
|
});
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
resumeRequestsOnPageActive,
|
||||||
|
cancelRequestsOnPageInactive
|
||||||
|
}));
|
||||||
|
|
||||||
const renderEmpty = (type?: string) => {
|
const renderEmpty = (type?: string) => {
|
||||||
if (type !== 'Table') return;
|
if (type !== 'Table') return;
|
||||||
@@ -49,21 +82,46 @@ const InstanceView: React.FC = () => {
|
|||||||
loading={dataSource.loading}
|
loading={dataSource.loading}
|
||||||
loadend={dataSource.loadend}
|
loadend={dataSource.loadend}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
image={<IconFont type="icon-extension-outline" />}
|
image={<IconFont type="icon-instance-outline" />}
|
||||||
filters={_.omit(queryParams, ['sort_by'])}
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
noFoundText={intl.formatMessage({
|
noFoundText={intl.formatMessage({
|
||||||
id: 'noresult.providers.nofound'
|
id: 'noresult.instances.nofound'
|
||||||
})}
|
})}
|
||||||
title={intl.formatMessage({ id: 'noresult.providers.title' })}
|
title={intl.formatMessage({ id: 'noresult.instances.title' })}
|
||||||
subTitle={intl.formatMessage({
|
subTitle={intl.formatMessage({
|
||||||
id: 'noresult.providers.subTitle'
|
id: 'noresult.instances.subTitle'
|
||||||
})}
|
})}
|
||||||
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
></NoResult>
|
></NoResult>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = useInstanceColumns(handleSelect);
|
const handleonClusterChange = (value: number) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
cluster_id: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStatusChange = (value: string) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
state: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnWorkerChange = (value: number) => {
|
||||||
|
handleQueryChange({
|
||||||
|
page: 1,
|
||||||
|
worker_id: value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = useInstanceColumns({
|
||||||
|
handleSelect,
|
||||||
|
clusterList,
|
||||||
|
workerList
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -77,6 +135,17 @@ const InstanceView: React.FC = () => {
|
|||||||
handleInputChange={handleNameChange}
|
handleInputChange={handleNameChange}
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
handleDeleteByBatch={handleDeleteBatch}
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
|
left={
|
||||||
|
<LeftFilters
|
||||||
|
handleNameChange={handleNameChange}
|
||||||
|
handleClusterChange={handleonClusterChange}
|
||||||
|
handleStatusChange={handleStatusChange}
|
||||||
|
handleWorkerChange={handleOnWorkerChange}
|
||||||
|
handleSearch={handleSearch}
|
||||||
|
clusterList={clusterList}
|
||||||
|
workerList={workerList}
|
||||||
|
></LeftFilters>
|
||||||
|
}
|
||||||
></FilterBar>
|
></FilterBar>
|
||||||
<ConfigProvider renderEmpty={renderEmpty}>
|
<ConfigProvider renderEmpty={renderEmpty}>
|
||||||
<Table
|
<Table
|
||||||
@@ -103,9 +172,18 @@ const InstanceView: React.FC = () => {
|
|||||||
></Table>
|
></Table>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</PageBox>
|
</PageBox>
|
||||||
|
<ViewLogsModal
|
||||||
|
status={openViewLogsModalStatus.currentData.status}
|
||||||
|
url={openViewLogsModalStatus.currentData.url}
|
||||||
|
tail={openViewLogsModalStatus.currentData.tail}
|
||||||
|
id={openViewLogsModalStatus.currentData.id}
|
||||||
|
modelId={openViewLogsModalStatus.currentData.modelId}
|
||||||
|
open={openViewLogsModalStatus.open}
|
||||||
|
onCancel={closeViewLogsModal}
|
||||||
|
></ViewLogsModal>
|
||||||
<DeleteModal ref={modalRef}></DeleteModal>
|
<DeleteModal ref={modalRef}></DeleteModal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default InstanceView;
|
export default InstanceView;
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import BaseSelect from '@/components/seal-form/base/select';
|
||||||
|
import { ListItem as workerListItem } from '@/pages/resources/config/types';
|
||||||
|
import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Input, Space } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import useFilterStatus from '../hooks/use-filter-status';
|
||||||
|
|
||||||
|
interface LeftFiltersProps {
|
||||||
|
handleNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
handleClusterChange: (value: number) => void;
|
||||||
|
handleStatusChange: (value: string) => void;
|
||||||
|
handleWorkerChange: (value: number) => void;
|
||||||
|
handleSearch: () => void;
|
||||||
|
clusterList: Global.BaseOption<number>[];
|
||||||
|
workerList: workerListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeftFilters: React.FC<LeftFiltersProps> = (props) => {
|
||||||
|
const {
|
||||||
|
handleNameChange,
|
||||||
|
handleClusterChange,
|
||||||
|
handleStatusChange,
|
||||||
|
handleWorkerChange,
|
||||||
|
handleSearch,
|
||||||
|
clusterList,
|
||||||
|
workerList
|
||||||
|
} = props;
|
||||||
|
const { labelRender, optionRender, statusOptions } = useFilterStatus({
|
||||||
|
optionList: [
|
||||||
|
{
|
||||||
|
label: 'Running',
|
||||||
|
value: 'running',
|
||||||
|
color: 'var(--ant-color-success)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Error',
|
||||||
|
value: 'error',
|
||||||
|
color: 'var(--ant-color-error)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Provisioning',
|
||||||
|
value: 'provisioning',
|
||||||
|
color: 'var(--ant-blue-5)'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Input
|
||||||
|
prefix={
|
||||||
|
<SearchOutlined
|
||||||
|
style={{ color: 'var(--ant-color-text-placeholder)' }}
|
||||||
|
></SearchOutlined>
|
||||||
|
}
|
||||||
|
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
|
||||||
|
style={{ width: 200 }}
|
||||||
|
size="large"
|
||||||
|
allowClear
|
||||||
|
onChange={handleNameChange}
|
||||||
|
></Input>
|
||||||
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'clusters.filterBy.cluster'
|
||||||
|
})}
|
||||||
|
style={{ width: 160 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
onChange={handleClusterChange}
|
||||||
|
options={clusterList}
|
||||||
|
></BaseSelect>
|
||||||
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'resources.filter.worker'
|
||||||
|
})}
|
||||||
|
style={{ width: 160 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
onChange={handleWorkerChange}
|
||||||
|
options={workerList.map((worker) => ({
|
||||||
|
label: worker.name,
|
||||||
|
value: worker.id
|
||||||
|
}))}
|
||||||
|
></BaseSelect>
|
||||||
|
<BaseSelect
|
||||||
|
allowClear
|
||||||
|
showSearch={false}
|
||||||
|
placeholder={intl.formatMessage({ id: 'common.filter.status' })}
|
||||||
|
style={{ width: 180 }}
|
||||||
|
size="large"
|
||||||
|
maxTagCount={1}
|
||||||
|
optionRender={optionRender}
|
||||||
|
labelRender={labelRender}
|
||||||
|
options={statusOptions}
|
||||||
|
onChange={handleStatusChange}
|
||||||
|
></BaseSelect>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
style={{ color: 'var(--ant-color-text-tertiary)' }}
|
||||||
|
onClick={handleSearch}
|
||||||
|
icon={<SyncOutlined></SyncOutlined>}
|
||||||
|
></Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LeftFilters;
|
||||||
@@ -1,16 +1,39 @@
|
|||||||
// columns.ts
|
// columns.ts
|
||||||
import DropdownButtons from '@/components/drop-down-buttons';
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import { tableSorter } from '@/config/settings';
|
import { tableSorter } from '@/config/settings';
|
||||||
|
import { ListItem as workerListItem } from '@/pages/resources/config/types';
|
||||||
|
import { convertFileSize } from '@/utils';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import _ from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import ActionsCell from '../components/instance-cells/actions-cell';
|
||||||
|
import DistributeInfoCell from '../components/instance-cells/distribute-info-cell';
|
||||||
|
import DownloadingStatusCell from '../components/instance-cells/downloading-status-cell';
|
||||||
|
import InstanceStatusCell from '../components/instance-cells/instance-status-cell';
|
||||||
|
import NameCell from '../components/instance-cells/name-cell';
|
||||||
import { ModelInstanceListItem as ListItem } from '../config/types';
|
import { ModelInstanceListItem as ListItem } from '../config/types';
|
||||||
|
|
||||||
const useProviderColumns = (
|
const calcTotalVram = (vram: Record<string, number>) => {
|
||||||
handleSelect: (val: string, record: ListItem) => void,
|
return _.sum(_.values(vram));
|
||||||
onCellClick?: (record: ListItem, dataIndex: string) => void
|
};
|
||||||
): ColumnsType<ListItem> => {
|
|
||||||
|
const useProviderColumns = (options: {
|
||||||
|
workerList: workerListItem[];
|
||||||
|
clusterList: Global.BaseOption<
|
||||||
|
number,
|
||||||
|
{
|
||||||
|
provider: string;
|
||||||
|
state: string;
|
||||||
|
is_default: boolean;
|
||||||
|
}
|
||||||
|
>[];
|
||||||
|
handleSelect: (val: string, record: ListItem) => void;
|
||||||
|
onCellClick?: (record: ListItem, dataIndex: string) => void;
|
||||||
|
}): ColumnsType<ListItem> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { workerList, clusterList, handleSelect, onCellClick } = options;
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@@ -19,30 +42,87 @@ const useProviderColumns = (
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
minWidth: 160,
|
minWidth: 160,
|
||||||
span: 5,
|
render: (value: string, record: ListItem) => (
|
||||||
render: () => '--'
|
<NameCell
|
||||||
|
record={record}
|
||||||
|
modelData={{
|
||||||
|
backend: record.backend,
|
||||||
|
backend_version: record.backend_version
|
||||||
|
}}
|
||||||
|
></NameCell>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'providers.table.providerName' }),
|
title: intl.formatMessage({ id: 'clusters.title' }),
|
||||||
dataIndex: ['config', 'type'],
|
dataIndex: 'cluster_id',
|
||||||
sorter: tableSorter(2),
|
|
||||||
span: 4,
|
|
||||||
minWidth: 160,
|
|
||||||
render: () => '--'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: 'providers.table.models' }),
|
|
||||||
dataIndex: 'models',
|
|
||||||
span: 3,
|
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
render: () => '--'
|
render: (text: number) => (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{text
|
||||||
|
? clusterList.find((cluster) => cluster.value === text)?.label
|
||||||
|
: ''}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'resources.worker' }),
|
||||||
|
dataIndex: 'worker_id',
|
||||||
|
minWidth: 200,
|
||||||
|
render: (text, record) => (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{text &&
|
||||||
|
(!record.distributed_servers?.subordinate_workers ||
|
||||||
|
!record.distributed_servers?.subordinate_workers.length)
|
||||||
|
? workerList.find((worker) => worker.id === text)?.name
|
||||||
|
: ''}
|
||||||
|
<DistributeInfoCell
|
||||||
|
record={record}
|
||||||
|
workerList={workerList}
|
||||||
|
></DistributeInfoCell>
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'models.table.vram.allocated' }),
|
||||||
|
dataIndex: 'allocated_vram',
|
||||||
|
sorter: tableSorter(6),
|
||||||
|
render: (text, record) => (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{convertFileSize(
|
||||||
|
record.computed_resource_claim?.vram
|
||||||
|
? calcTotalVram(record.computed_resource_claim?.vram)
|
||||||
|
: 0,
|
||||||
|
1
|
||||||
|
)}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({ id: 'common.table.status' }),
|
||||||
|
dataIndex: 'state',
|
||||||
|
width: 160,
|
||||||
|
render: (text: string, record: ListItem) => (
|
||||||
|
<span style={{ gap: 4 }} className="flex-center">
|
||||||
|
<InstanceStatusCell record={record} onSelect={() => {}} />
|
||||||
|
<DownloadingStatusCell
|
||||||
|
backend={record?.backend}
|
||||||
|
distributed_servers={record.distributed_servers}
|
||||||
|
workerList={[]}
|
||||||
|
record={record}
|
||||||
|
></DownloadingStatusCell>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||||
dataIndex: 'created_at',
|
dataIndex: 'created_at',
|
||||||
sorter: tableSorter(6),
|
sorter: tableSorter(6),
|
||||||
span: 3,
|
span: 3,
|
||||||
render: () => '--'
|
render: (text, record) => (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{dayjs(record.created_at).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</AutoTooltip>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.operation' }),
|
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||||
@@ -50,14 +130,17 @@ const useProviderColumns = (
|
|||||||
span: 3,
|
span: 3,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
render: (value: string, record: ListItem) => (
|
render: (value: string, record: ListItem) => (
|
||||||
<DropdownButtons
|
<ActionsCell
|
||||||
items={[]}
|
record={record}
|
||||||
onSelect={(val) => handleSelect(val, record)}
|
modelData={{
|
||||||
></DropdownButtons>
|
categories: record.categories || []
|
||||||
|
}}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
></ActionsCell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, [handleSelect, onCellClick]);
|
}, [handleSelect, onCellClick, workerList, clusterList]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useProviderColumns;
|
export default useProviderColumns;
|
||||||
|
|||||||
Reference in New Issue
Block a user