fix: my models status
This commit is contained in:
+14
-4
@@ -13,6 +13,7 @@ import {
|
||||
writeState
|
||||
} from '@/utils/localstore/index';
|
||||
import { RequestConfig, history } from '@umijs/max';
|
||||
import { message } from 'antd';
|
||||
|
||||
const loginPath = '/login';
|
||||
|
||||
@@ -53,13 +54,24 @@ export async function getInitialState(): Promise<{
|
||||
}): Promise<Global.UserInfo> => {
|
||||
try {
|
||||
const data = await queryCurrentUserState({
|
||||
skipErrorHandler: config?.skipErrorHandler ?? false
|
||||
skipErrorHandler: true
|
||||
});
|
||||
if (data.is_admin) {
|
||||
getUpdateCheck();
|
||||
}
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
const data = error?.response?.data;
|
||||
if (data?.code === 401 && data?.message.includes('deactivate')) {
|
||||
message.error({
|
||||
content: (
|
||||
<div>
|
||||
<span>{data?.message}</span>
|
||||
</div>
|
||||
),
|
||||
duration: 5
|
||||
});
|
||||
}
|
||||
history.push(loginPath);
|
||||
}
|
||||
return {} as Global.UserInfo;
|
||||
@@ -81,9 +93,7 @@ export async function getInitialState(): Promise<{
|
||||
getAppVersionInfo();
|
||||
|
||||
if (![loginPath].includes(location.pathname)) {
|
||||
const userInfo = await fetchUserInfo({
|
||||
skipErrorHandler: location.pathname === '/'
|
||||
});
|
||||
const userInfo = await fetchUserInfo();
|
||||
checkDefaultPage(userInfo);
|
||||
return {
|
||||
fetchUserInfo,
|
||||
|
||||
@@ -8,7 +8,7 @@ import './index.less';
|
||||
interface TagsWrapperProps {
|
||||
gap?: number;
|
||||
dataList: any[];
|
||||
renderTag: (item: any) => React.ReactNode;
|
||||
renderTag: (item: any, index?: number) => React.ReactNode;
|
||||
}
|
||||
|
||||
const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
||||
@@ -94,7 +94,7 @@ const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
||||
{_.map(
|
||||
_.slice(dataList, hiddenIndices.start, hiddenIndices.end),
|
||||
(item: any, index: number) => {
|
||||
return <span key={index}>{renderTag?.(item)}</span>;
|
||||
return <span key={index}>{renderTag?.(item, index)}</span>;
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@ const TagsWrapper: React.FC<TagsWrapperProps> = (props) => {
|
||||
_.slice(dataList, hiddenIndices.end),
|
||||
(item: any, index: number) => {
|
||||
return {
|
||||
label: renderTag?.(item),
|
||||
label: renderTag?.(item, index),
|
||||
key: index,
|
||||
onClick: handleClick
|
||||
};
|
||||
|
||||
@@ -261,5 +261,6 @@ export default {
|
||||
'common.button.edit.item': 'Edit {name}',
|
||||
'common.button.terminal': 'Terminal',
|
||||
'common.button.addItem': 'Add Item',
|
||||
'common.help.default': 'Default: {content}'
|
||||
'common.help.default': 'Default: {content}',
|
||||
'common.filter.status': 'Filter by status'
|
||||
};
|
||||
|
||||
@@ -203,5 +203,8 @@ export default {
|
||||
'models.form.gpusAllocationType.auto.tips':
|
||||
'System calculates GPUs per replica automatically.',
|
||||
'models.form.gpusAllocationType.custom.tips':
|
||||
'You can specify the exact number of GPUs per replica.'
|
||||
'You can specify the exact number of GPUs per replica.',
|
||||
'models.mymodels.status.inactive': 'Stopped',
|
||||
'models.mymodels.status.degrade': 'Abnormal',
|
||||
'models.mymodels.status.active': 'Active'
|
||||
};
|
||||
|
||||
@@ -261,7 +261,8 @@ export default {
|
||||
'common.button.edit.item': 'Edit {name}',
|
||||
'common.button.terminal': 'Terminal',
|
||||
'common.button.addItem': 'Add Item',
|
||||
'common.help.default': 'Default: {content}'
|
||||
'common.help.default': 'Default: {content}',
|
||||
'common.filter.status': 'Filter by status'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -288,5 +289,6 @@ export default {
|
||||
// 21. 'common.button.edit.item': 'Edit {name}',
|
||||
// 22. 'common.button.terminal': 'Terminal',
|
||||
// 23. 'common.button.addItem': 'Add Item',
|
||||
// 24. 'common.help.default': 'Default: {content}'
|
||||
// 24. 'common.help.default': 'Default: {content}',
|
||||
// 25. 'common.filter.status': 'Filter by status'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -200,7 +200,10 @@ export default {
|
||||
'models.form.gpusAllocationType.auto.tips':
|
||||
'System calculates GPUs per replica automatically.',
|
||||
'models.form.gpusAllocationType.custom.tips':
|
||||
'You can specify the exact number of GPUs per replica.'
|
||||
'You can specify the exact number of GPUs per replica.',
|
||||
'models.mymodels.status.inactive': 'Stopped',
|
||||
'models.mymodels.status.degrade': 'Abnormal',
|
||||
'models.mymodels.status.active': 'Active'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -240,6 +243,9 @@ export default {
|
||||
// 35. 'models.form.gpusAllocationType.auto': 'Auto',
|
||||
// 36. 'models.form.gpusAllocationType.custom': 'Custom',
|
||||
// 37. 'models.form.gpusAllocationType.auto.tips': 'System calculates GPUs per replica automatically.',
|
||||
// 38. 'models.form.gpusAllocationType.custom.tips': 'You can specify the exact number of GPUs per replica.'
|
||||
// 38. 'models.form.gpusAllocationType.custom.tips': 'You can specify the exact number of GPUs per replica.',
|
||||
// 39. 'models.mymodels.status.inactive': 'Stopped',
|
||||
// 41. 'models.mymodels.status.degrade': 'Abnormal',
|
||||
// 42. 'models.mymodels.status.active': 'Active'
|
||||
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -260,9 +260,10 @@ export default {
|
||||
'common.button.edit.item': 'Редактировать {name}',
|
||||
'common.button.terminal': 'Терминал',
|
||||
'common.button.addItem': 'Добавить элемент',
|
||||
'common.help.default': 'По умолчанию: {content}'
|
||||
'common.help.default': 'По умолчанию: {content}',
|
||||
'common.filter.status': 'Filter by status'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
|
||||
// 1. 'common.filter.status': 'Filter by status'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -187,20 +187,30 @@ export default {
|
||||
'models.form.chunkSize': 'Размер чанков кэша',
|
||||
'models.form.maxCPUSize': 'Максимальный размер CPU кэша (ГиБ)',
|
||||
'models.form.remoteURL': 'URL удаленного хранилища',
|
||||
'models.form.runCommandPlaceholder': 'напр., vllm serve Qwen/Qwen2.5-1.5B-Instruct',
|
||||
'models.form.runCommandPlaceholder':
|
||||
'напр., vllm serve Qwen/Qwen2.5-1.5B-Instruct',
|
||||
'models.accessSettings.public': 'Публичный',
|
||||
'models.accessSettings.authed': 'Аутентифицированный',
|
||||
'models.accessSettings.allowedUsers': 'Разрешенные пользователи',
|
||||
'models.accessSettings.public.tips': 'При публичном доступе любой пользователь может получить доступ к модели без аутентификации, что может привести к риску утечки данных.',
|
||||
'models.accessSettings.public.tips':
|
||||
'При публичном доступе любой пользователь может получить доступ к модели без аутентификации, что может привести к риску утечки данных.',
|
||||
'models.table.button.deploy': 'Развернуть сейчас',
|
||||
'models.form.backendVersion.holder': 'Введите или выберите версию',
|
||||
'models.form.gpusperreplica': 'GPU на реплику',
|
||||
'models.form.gpusAllocationType': 'Тип распределения GPU',
|
||||
'models.form.gpusAllocationType.auto': 'Авто',
|
||||
'models.form.gpusAllocationType.custom': 'Вручную',
|
||||
'models.form.gpusAllocationType.auto.tips': 'Система автоматически рассчитывает количество GPU на реплику.',
|
||||
'models.form.gpusAllocationType.custom.tips': 'Вы можете указать точное количество GPU на реплику.'
|
||||
'models.form.gpusAllocationType.auto.tips':
|
||||
'Система автоматически рассчитывает количество GPU на реплику.',
|
||||
'models.form.gpusAllocationType.custom.tips':
|
||||
'Вы можете указать точное количество GPU на реплику.',
|
||||
'models.mymodels.status.inactive': 'Stopped',
|
||||
'models.mymodels.status.degrade': 'Abnormal',
|
||||
'models.mymodels.status.active': 'Active'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
|
||||
// 1. 'models.mymodels.status.inactive': 'Stopped',
|
||||
// 3. 'models.mymodels.status.degrade': 'Abnormal',
|
||||
// 4. 'models.mymodels.status.active': 'Active'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -253,5 +253,6 @@ export default {
|
||||
'common.button.edit.item': '编辑 {name}',
|
||||
'common.button.terminal': '终端',
|
||||
'common.button.addItem': '添加项',
|
||||
'common.help.default': '默认: {content}'
|
||||
'common.help.default': '默认: {content}',
|
||||
'common.filter.status': '按状态筛选'
|
||||
};
|
||||
|
||||
@@ -191,5 +191,8 @@ export default {
|
||||
'models.form.gpusAllocationType.custom': '自定义',
|
||||
'models.form.gpusAllocationType.auto.tips':
|
||||
'系统自动计算每个副本的 GPU 数量。',
|
||||
'models.form.gpusAllocationType.custom.tips': '您可以指定每个副本的 GPU 数量'
|
||||
'models.form.gpusAllocationType.custom.tips': '您可以指定每个副本的 GPU 数量',
|
||||
'models.mymodels.status.inactive': '已停止',
|
||||
'models.mymodels.status.degrade': '异常',
|
||||
'models.mymodels.status.active': '可用'
|
||||
};
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import TagWrapper from '@/components/tags-wrapper';
|
||||
import ThemeTag from '@/components/tags-wrapper/theme-tag';
|
||||
import Card from '@/components/templates/card';
|
||||
import { StatusMaps } from '@/config';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import { Button } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { modelCategories, modelCategoriesMap, modelSourceMap } from '../config';
|
||||
import {
|
||||
modelCategories,
|
||||
modelCategoriesMap,
|
||||
modelSourceMap,
|
||||
MyModelsStatusLabelMap,
|
||||
MyModelsStatusMap,
|
||||
MyModelsStatusValueMap
|
||||
} from '../config';
|
||||
import { categoryToPathMap } from '../config/button-actions';
|
||||
import { categoryConfig } from './model-tag';
|
||||
|
||||
const MyModelsStatusValueMap = {
|
||||
Inactive: 'Inactive',
|
||||
Starting: 'Starting',
|
||||
Degrade: 'Degrade',
|
||||
Active: 'Active'
|
||||
};
|
||||
|
||||
const MyModelsStatusMap = {
|
||||
[MyModelsStatusValueMap.Inactive]: StatusMaps.inactive,
|
||||
[MyModelsStatusValueMap.Starting]: StatusMaps.transitioning,
|
||||
[MyModelsStatusValueMap.Degrade]: StatusMaps.warning,
|
||||
[MyModelsStatusValueMap.Active]: StatusMaps.success
|
||||
};
|
||||
|
||||
const CardWrapper = styled.div`
|
||||
&:hover {
|
||||
.content {
|
||||
@@ -36,6 +29,14 @@ const CardWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Dot = styled.span`
|
||||
background-color: var(--ant-color-text-quaternary);
|
||||
border-radius: 50%;
|
||||
flex: none;
|
||||
height: 3px;
|
||||
width: 3px;
|
||||
`;
|
||||
|
||||
const ModelItemContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -63,6 +64,7 @@ const ModelItemContent = styled.div`
|
||||
font-weight: 400;
|
||||
}
|
||||
.extra-info {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -105,6 +107,14 @@ const sourceIconMap = {
|
||||
[modelSourceMap.modelscope_value]: 'icon-tu2'
|
||||
};
|
||||
|
||||
const renderTag = (item: any, index = 0) => {
|
||||
return (
|
||||
<ThemeTag key={item} className="tag-item" color="purple">
|
||||
{item}
|
||||
</ThemeTag>
|
||||
);
|
||||
};
|
||||
|
||||
const ModelItem: React.FC<{
|
||||
model: Record<string, any>;
|
||||
onClick: (model: any) => void;
|
||||
@@ -155,16 +165,15 @@ const ModelItem: React.FC<{
|
||||
if (!model.replicas && !model.ready_replicas) {
|
||||
return MyModelsStatusValueMap.Inactive;
|
||||
}
|
||||
|
||||
if (model.replicas > 0 && !model.ready_replicas) {
|
||||
return MyModelsStatusValueMap.Starting;
|
||||
}
|
||||
if (model.replicas > 1 && model.replicas !== model.ready_replicas) {
|
||||
return MyModelsStatusValueMap.Degrade;
|
||||
}
|
||||
|
||||
if (model.replicas === model.ready_replicas && model.replicas > 0) {
|
||||
return MyModelsStatusValueMap.Active;
|
||||
}
|
||||
return MyModelsStatusValueMap.Inactive;
|
||||
return MyModelsStatusValueMap.Degrade;
|
||||
}, [model]);
|
||||
|
||||
return (
|
||||
@@ -177,18 +186,20 @@ const ModelItem: React.FC<{
|
||||
ghost
|
||||
header={
|
||||
<Header>
|
||||
<span className="text">
|
||||
<span className="text gap-8">
|
||||
<IconFont
|
||||
type={sourceIconMap[model.source]}
|
||||
style={{ marginRight: 8, fontSize: 24 }}
|
||||
style={{ fontSize: 24 }}
|
||||
/>
|
||||
<span>{model.name}</span>
|
||||
</span>
|
||||
<StatusTag
|
||||
maxTooltipWidth={400}
|
||||
statusValue={{
|
||||
status: MyModelsStatusMap[status],
|
||||
text: status,
|
||||
status: MyModelsStatusMap[model.status],
|
||||
text: intl.formatMessage({
|
||||
id: MyModelsStatusLabelMap[model.status]
|
||||
}),
|
||||
message: model.state_message
|
||||
}}
|
||||
></StatusTag>
|
||||
@@ -214,16 +225,27 @@ const ModelItem: React.FC<{
|
||||
</ThemeTag>
|
||||
);
|
||||
})}
|
||||
|
||||
{maxToken > 0 && (
|
||||
<ThemeTag className="tag-item" color="purple">
|
||||
{maxToken}K context
|
||||
</ThemeTag>
|
||||
<>
|
||||
<Dot></Dot>
|
||||
<ThemeTag className="tag-item" color="purple">
|
||||
{maxToken}K context
|
||||
</ThemeTag>
|
||||
</>
|
||||
)}
|
||||
{model.meta?.voices?.length > 0 && (
|
||||
<>
|
||||
<Dot></Dot>
|
||||
<TagWrapper
|
||||
gap={8}
|
||||
dataList={model.meta?.voices}
|
||||
renderTag={renderTag}
|
||||
></TagWrapper>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{[
|
||||
MyModelsStatusValueMap.Active,
|
||||
MyModelsStatusValueMap.Degrade
|
||||
].includes(status) && (
|
||||
{[MyModelsStatusValueMap.Active].includes(model.status) && (
|
||||
<Button
|
||||
size="middle"
|
||||
className="btn"
|
||||
|
||||
@@ -188,6 +188,24 @@ export const status: any = {
|
||||
[InstanceStatusMap.Unreachable]: StatusMaps.error
|
||||
};
|
||||
|
||||
export const MyModelsStatusValueMap = {
|
||||
Inactive: 'Inactive',
|
||||
Degrade: 'Degrade',
|
||||
Active: 'Active'
|
||||
};
|
||||
|
||||
export const MyModelsStatusMap = {
|
||||
[MyModelsStatusValueMap.Inactive]: StatusMaps.inactive,
|
||||
[MyModelsStatusValueMap.Degrade]: StatusMaps.error,
|
||||
[MyModelsStatusValueMap.Active]: StatusMaps.success
|
||||
};
|
||||
|
||||
export const MyModelsStatusLabelMap = {
|
||||
[MyModelsStatusValueMap.Inactive]: 'models.mymodels.status.inactive',
|
||||
[MyModelsStatusValueMap.Degrade]: 'models.mymodels.status.degrade',
|
||||
[MyModelsStatusValueMap.Active]: 'models.mymodels.status.active'
|
||||
};
|
||||
|
||||
export const ScheduleValueMap = {
|
||||
Auto: 'auto',
|
||||
Manual: 'manual',
|
||||
@@ -334,7 +352,8 @@ export const excludeFields = [
|
||||
'backend',
|
||||
'gpu_selector',
|
||||
'run_command',
|
||||
'image_name'
|
||||
'image_name',
|
||||
'extended_kv_cache.enabled'
|
||||
];
|
||||
|
||||
// ingore fields when compare old and new data
|
||||
@@ -357,7 +376,8 @@ export const updateExcludeFields = [
|
||||
'gpu_selector',
|
||||
'categories',
|
||||
'env',
|
||||
'replicas'
|
||||
'replicas',
|
||||
'extended_kv_cache.enabled'
|
||||
];
|
||||
|
||||
export const formFields = [
|
||||
|
||||
@@ -214,8 +214,19 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getFieldPaths = (obj: Record<string, any>, prefix = ''): string => {
|
||||
const result = Object.entries(obj).flatMap(([key, value]) => {
|
||||
const path = prefix ? `${prefix}.${key}` : key;
|
||||
return typeof value === 'object' && value !== null
|
||||
? getFieldPaths(value, path)
|
||||
: [path];
|
||||
});
|
||||
|
||||
return result[0] || '';
|
||||
};
|
||||
|
||||
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
|
||||
const fieldName = Object.keys(changedValues)[0];
|
||||
const fieldName = getFieldPaths(changedValues);
|
||||
|
||||
if (excludeFields.includes(fieldName)) {
|
||||
return;
|
||||
|
||||
@@ -3,13 +3,32 @@ import SealInputNumber from '@/components/seal-form/input-number';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import { useFormContext } from '../config/form-context';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
const KVCacheForm = () => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance();
|
||||
const { onValuesChange } = useFormContext();
|
||||
const kvCacheEnabled = Form.useWatch(['extended_kv_cache', 'enabled'], form);
|
||||
|
||||
const handleOnChange = async (e: any) => {
|
||||
if (e.target.checked) {
|
||||
form.setFieldsValue({
|
||||
extended_kv_cache: {
|
||||
enabled: true,
|
||||
chunk_size: 256,
|
||||
max_local_cpu_size: 10,
|
||||
remote_url: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 200);
|
||||
});
|
||||
onValuesChange?.({}, form.getFieldsValue());
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ paddingBottom: 22 }}>
|
||||
@@ -20,6 +39,7 @@ const KVCacheForm = () => {
|
||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||
>
|
||||
<CheckboxField
|
||||
onChange={handleOnChange}
|
||||
label={intl.formatMessage({ id: 'models.form.extendedkvcache' })}
|
||||
></CheckboxField>
|
||||
</Form.Item>
|
||||
|
||||
@@ -9,14 +9,41 @@ import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useIntl, useNavigate } from '@umijs/max';
|
||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||
import { Button, Input, Space } from 'antd';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { ScrollerContext } from '../_components/infinite-scroller/use-scroller-context';
|
||||
import NoResult from '../_components/no-result';
|
||||
import { MY_MODELS_API, queryMyModels } from './apis';
|
||||
import ModelItem from './components/model-item';
|
||||
import { categoryOptions, modelCategoriesMap } from './config';
|
||||
import {
|
||||
categoryOptions,
|
||||
modelCategoriesMap,
|
||||
MyModelsStatusValueMap
|
||||
} from './config';
|
||||
import { categoryToPathMap } from './config/button-actions';
|
||||
|
||||
const Dot = ({ color }: { color: string }) => {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '50%',
|
||||
height: 8,
|
||||
width: 8,
|
||||
display: 'flex'
|
||||
}}
|
||||
></span>
|
||||
);
|
||||
};
|
||||
|
||||
const optionRender = (item: any) => {
|
||||
return (
|
||||
<span className="flex-center gap-8">
|
||||
<Dot color={item.data?.color}></Dot>
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const UserModels: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
@@ -36,6 +63,33 @@ const UserModels: React.FC = () => {
|
||||
}
|
||||
});
|
||||
const intl = useIntl();
|
||||
const [status, setStatus] = React.useState<string | undefined>(undefined);
|
||||
|
||||
const statusOptions = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
value: MyModelsStatusValueMap.Active,
|
||||
color: 'var(--ant-color-success)',
|
||||
label: intl.formatMessage({
|
||||
id: 'models.mymodels.status.active'
|
||||
})
|
||||
},
|
||||
{
|
||||
value: MyModelsStatusValueMap.Inactive,
|
||||
color: 'var(--ant-color-fill)',
|
||||
label: intl.formatMessage({
|
||||
id: 'models.mymodels.status.inactive'
|
||||
})
|
||||
},
|
||||
{
|
||||
value: MyModelsStatusValueMap.Degrade,
|
||||
color: 'var(--ant-color-error)',
|
||||
label: intl.formatMessage({
|
||||
id: 'models.mymodels.status.degrade'
|
||||
})
|
||||
}
|
||||
];
|
||||
}, [intl]);
|
||||
|
||||
const handleCategoryChange = (value: string) => {
|
||||
handleQueryChange({
|
||||
@@ -77,6 +131,46 @@ const UserModels: React.FC = () => {
|
||||
});
|
||||
});
|
||||
|
||||
const handleStatusChange = (value: string) => {
|
||||
setStatus(value);
|
||||
};
|
||||
|
||||
const labelRender = (item: any) => {
|
||||
const current = statusOptions.find((option) => option.value === item.value);
|
||||
return (
|
||||
<span className="flex-center gap-8">
|
||||
<Dot color={current!.color}></Dot>
|
||||
{item.label}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const getStatus = useCallback((model: any) => {
|
||||
if (!model.replicas && !model.ready_replicas) {
|
||||
return MyModelsStatusValueMap.Inactive;
|
||||
}
|
||||
|
||||
if (model.replicas > 0 && !model.ready_replicas) {
|
||||
return MyModelsStatusValueMap.Degrade;
|
||||
}
|
||||
|
||||
if (model.replicas === model.ready_replicas && model.replicas > 0) {
|
||||
return MyModelsStatusValueMap.Active;
|
||||
}
|
||||
return MyModelsStatusValueMap.Degrade;
|
||||
}, []);
|
||||
|
||||
const dataList = useMemo(() => {
|
||||
const result = dataSource.dataList.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
status: getStatus(item)
|
||||
};
|
||||
});
|
||||
if (!status) return result;
|
||||
return result.filter((item) => item.status === status);
|
||||
}, [dataSource.dataList, status]);
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
ghost
|
||||
@@ -117,6 +211,18 @@ const UserModels: React.FC = () => {
|
||||
options={categoryOptions}
|
||||
onChange={handleCategoryChange}
|
||||
></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)' }}
|
||||
@@ -135,7 +241,7 @@ const UserModels: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<CardList
|
||||
dataList={dataSource.dataList}
|
||||
dataList={dataList}
|
||||
loading={dataSource.loading}
|
||||
activeId={false}
|
||||
isFirst={!dataSource.loadend}
|
||||
@@ -145,9 +251,9 @@ const UserModels: React.FC = () => {
|
||||
<NoResult
|
||||
loading={dataSource.loading}
|
||||
loadend={dataSource.loadend}
|
||||
dataSource={dataSource.dataList}
|
||||
dataSource={dataList}
|
||||
image={<IconFont type="icon-models" />}
|
||||
filters={queryParams}
|
||||
filters={{ ...queryParams, status: status }}
|
||||
noFoundText={intl.formatMessage({
|
||||
id: 'noresult.mymodels.nofound'
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user