fix(style): cluster state do not display

This commit is contained in:
jialin
2025-10-14 13:09:22 +08:00
parent 903500dcfa
commit 620c113fae
28 changed files with 200 additions and 198 deletions
+10 -11
View File
@@ -104,17 +104,6 @@ export default [
defaultIcon: 'icon-models',
access: 'canSeeUser',
component: './llmodels/user-models'
},
{
name: 'backendsList',
path: '/models/backends/list',
key: 'backendsList',
icon: 'icon-backend',
selectedIcon: 'icon-backend-filled',
defaultIcon: 'icon-backend',
access: 'canSeeAdmin',
subMenu: ['/models/backends/create'],
component: './backends/index'
}
]
},
@@ -154,6 +143,16 @@ export default [
selectedIcon: 'icon-files-filled',
defaultIcon: 'icon-files',
component: './resources/components/model-files'
},
{
name: 'backendsList',
path: '/resources/backends',
key: 'backendsList',
icon: 'icon-backend',
selectedIcon: 'icon-backend-filled',
defaultIcon: 'icon-backend',
access: 'canSeeAdmin',
component: './backends/index'
}
]
},
+2 -1
View File
@@ -50,7 +50,8 @@ const icons = {
Yaml: React.createElement(IconFont, { type: 'icon-code_block' }),
Version: React.createElement(IconFont, { type: 'icon-version' }),
Parameter: React.createElement(IconFont, { type: 'icon-parameters' }),
Private: React.createElement(IconFont, { type: 'icon-private' })
Private: React.createElement(IconFont, { type: 'icon-private' }),
AWS: React.createElement(IconFont, { type: 'icon-aws' })
};
export default icons;
+1 -1
View File
@@ -2,7 +2,7 @@ import { createFromIconfontCN } from '@ant-design/icons';
// import './iconfont/iconfont.js';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4613488_lg2z989p7f.js'
scriptUrl: '//at.alicdn.com/t/c/font_4613488_ab78o2vlum.js'
});
export default IconFont;
+5 -1
View File
@@ -3,6 +3,8 @@ import React from 'react';
interface CatalogSkeltonProps {
span: number;
skeletonProps?: any;
skeletonStyle?: React.CSSProperties;
}
const CardSkelton: React.FC<CatalogSkeltonProps> = (props) => {
@@ -21,8 +23,10 @@ const CardSkelton: React.FC<CatalogSkeltonProps> = (props) => {
style={{
border: '1px solid var(--ant-color-border)',
borderRadius: 'var(--border-radius-base)',
padding: '16px 16px'
padding: '16px 16px',
...props.skeletonStyle
}}
{...(props.skeletonProps || {})}
></Skeleton>
</Col>
);
+12 -1
View File
@@ -22,6 +22,7 @@ export default function useTableFetch<T>(
defaultData?: any[];
events?: EventsType[];
defaultQueryParams?: Record<string, any>;
isInfiniteScroll?: boolean;
} & WatchConfig
) {
const {
@@ -33,7 +34,8 @@ export default function useTableFetch<T>(
watch,
defaultData = [],
events = ['UPDATE', 'DELETE'],
defaultQueryParams = {}
defaultQueryParams = {},
isInfiniteScroll = false
} = options;
const pollingRef = useRef<any>(null);
const chunkRequedtRef = useRef<any>(null);
@@ -141,6 +143,9 @@ export default function useTableFetch<T>(
total: newRes.pagination.total,
totalPage: newRes.pagination.totalPage
});
if (isInfiniteScroll) {
setQueryParams(newParams);
}
return;
}
@@ -151,6 +156,12 @@ export default function useTableFetch<T>(
total: res.pagination.total,
totalPage: res.pagination.totalPage
});
if (isInfiniteScroll && query?.page) {
setQueryParams({
...queryParams,
...query
});
}
} catch (error) {
console.log('error', error);
setDataSource({
+1 -1
View File
@@ -32,5 +32,5 @@ export default {
'menu.clusterManagement.credentials': 'Cloud Credentials',
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
'menu.clusterManagement.clusterCreate': 'Create Cluster',
'menu.models.backendsList': 'Inference Backends'
'menu.resources.backendsList': 'Inference Backends'
};
+2 -2
View File
@@ -32,7 +32,7 @@ export default {
'menu.models.userModels': 'My Models',
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
'menu.clusterManagement.clusterCreate': 'Create Cluster',
'menu.models.backendsList': 'Inference Backends'
'menu.resources.backendsList': 'Inference Backends'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -49,5 +49,5 @@ export default {
// 11. 'menu.models.userModels': 'My Models'
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster',
// 14. 'menu.models.backendsList': 'Inference Backends'
// 14. 'menu.resources.backendsList': 'Inference Backends'
// ========== End of To-Do List ==========
+2 -2
View File
@@ -32,9 +32,9 @@ export default {
'menu.models.userModels': 'Мои модели',
'menu.clusterManagement.clusterDetail': 'Детали кластера',
'menu.clusterManagement.clusterCreate': 'Создать кластер',
'menu.models.backendsList': 'Inference Backends'
'menu.resources.backendsList': 'Inference Backends'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
// 14. 'menu.models.backendsList': 'Inference Backends'
// 14. 'menu.resources.backendsList': 'Inference Backends'
// ========== End of To-Do List ==========
+1 -1
View File
@@ -32,5 +32,5 @@ export default {
'menu.clusterManagement.credentials': '云凭证',
'menu.clusterManagement.clusterDetail': '集群详情',
'menu.clusterManagement.clusterCreate': '创建集群',
'menu.models.backendsList': '推理后端'
'menu.resources.backendsList': '推理后端'
};
@@ -227,7 +227,7 @@ const BackendCard: React.FC<BackendCardProps> = ({ data, onSelect }) => {
color="geekblue"
className="font-400"
bordered={false}
style={{ borderRadius: 'var(--ant-border-radius)' }}
style={{ borderRadius: 'var(--ant-border-radius)', margin: 0 }}
>
{intl.formatMessage({ id: 'backend.builtin' })}
</Tag>
+29 -7
View File
@@ -24,6 +24,20 @@ const SpinWrapper = styled.div`
}
`;
const SkeletonWrapper = styled.div`
.ant-skeleton-content {
.ant-skeleton-paragraph {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 0;
li {
margin-top: 0 !important;
}
}
}
`;
interface BackendListProps {
defaultSpan?: number;
resizable?: boolean;
@@ -34,12 +48,6 @@ interface BackendListProps {
onSelect?: (item: any) => void;
}
const InnerSkeleton: React.FC<{
span: number;
}> = (props) => {
return <CardSkeleton {...props} />;
};
const ListSkeleton: React.FC<{
span: number;
loading: boolean;
@@ -56,7 +64,21 @@ const ListSkeleton: React.FC<{
}}
wrapperClassName="skelton-wrapper"
>
{isFirst && <InnerSkeleton span={span}></InnerSkeleton>}
{isFirst && (
<SkeletonWrapper>
<CardSkeleton
span={span}
skeletonProps={{
title: false
}}
skeletonStyle={{
display: 'flex',
flexDirection: 'column',
gap: 26
}}
></CardSkeleton>
</SkeletonWrapper>
)}
</Spin>
</SpinWrapper>
)}
+3 -2
View File
@@ -1,11 +1,12 @@
import CollapsibleContainer from '@/components/collapse-container';
import BaseSelect from '@/components/seal-form/base/select';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Empty, Form, Select, Tag } from 'antd';
import { Button, Empty, Form, Tag } from 'antd';
import React, { useEffect, useMemo } from 'react';
import styled from 'styled-components';
import { frameworks } from '../config';
@@ -163,7 +164,7 @@ const VersionsForm: React.FC<AddModalProps> = ({
</Button>
</span>
{!currentData?.is_built_in && (
<Select
<BaseSelect
prefix={
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
{intl.formatMessage({ id: 'backend.isDefault' })}:
+2 -1
View File
@@ -45,6 +45,7 @@ const BackendList = () => {
deleteAPI: deleteBackend,
API: INFERENCE_BACKEND_API,
watch: false,
isInfiniteScroll: true,
contentForDelete: 'backends.title',
defaultQueryParams: {
perPage: 100
@@ -154,7 +155,7 @@ const BackendList = () => {
<PageContainer
ghost
header={{
title: intl.formatMessage({ id: 'menu.models.backendsList' }),
title: intl.formatMessage({ id: 'menu.resources.backendsList' }),
style: {
paddingInline: 'var(--layout-content-header-inlinepadding)'
},
@@ -16,7 +16,6 @@ const Title = styled.div`
height: 40px;
align-items: center;
gap: 16px;
// background-color: var(--ant-color-fill-secondary);
border-radius: var(--ant-border-radius);
margin-bottom: 22px;
font-weight: 600;
+6 -71
View File
@@ -1,12 +1,11 @@
import icons from '@/components/icon-font/icons';
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
import React from 'react';
export const ClusterStatusValueMap = {
Provisioning: 0,
Ready: 3,
Provisioned: 1
Provisioning: 'provisioning',
Ready: 'ready',
Provisioned: 'provisioned'
};
export const ClusterStatusLabelMap = {
@@ -26,8 +25,9 @@ export const ProviderValueMap = {
DigitalOcean: 'DigitalOcean',
Docker: 'Docker',
HuaweiCloud: 'HuaweiCloud',
AliCloud: 'AliCloud',
TencentCloud: 'TencentCloud'
AliCloud: 'AlibabaCloud',
TencentCloud: 'TencentCloud',
AWS: 'AWS'
};
export type ProviderType = keyof typeof ProviderValueMap | null | undefined;
@@ -68,71 +68,6 @@ export const vendorIconMap = {
fedora: 'icon-fedora'
};
export const providerList = [
{
label: 'clusters.provider.custom',
locale: true,
value: ProviderValueMap.Docker,
key: ProviderValueMap.Docker,
icon: React.cloneElement(icons.Docker, {
style: { color: 'var(--ant-color-primary)' }
}),
group: 'Self-Managed'
},
{
label: 'Kubernetes',
locale: false,
value: ProviderValueMap.Kubernetes,
key: ProviderValueMap.Kubernetes,
icon: React.cloneElement(icons.KubernetesOutlined, {
style: { color: 'var(--ant-color-primary)' }
}),
group: 'Self-Managed'
},
{
label: 'DigitalOcean',
locale: false,
value: ProviderValueMap.DigitalOcean,
key: ProviderValueMap.DigitalOcean,
icon: React.cloneElement(icons.DigitalOcean, {
style: {
color: 'var(--ant-color-primary)'
}
}),
group: 'Cloud Provider'
},
{
label: 'Huawei Cloud',
locale: false,
disabled: true,
value: ProviderValueMap.HuaweiCloud,
key: ProviderValueMap.HuaweiCloud,
icon: icons.HuaweiCloud,
description: 'Comming soon',
group: 'Cloud Provider'
},
{
label: 'Ali Cloud',
locale: false,
disabled: true,
value: ProviderValueMap.AliCloud,
key: ProviderValueMap.AliCloud,
icon: icons.AliCloud,
description: 'Comming soon',
group: 'Cloud Provider'
},
{
label: 'Tencent Cloud',
locale: false,
disabled: true,
value: ProviderValueMap.TencentCloud,
key: ProviderValueMap.TencentCloud,
icon: icons.TencentCloud,
description: 'Comming soon',
group: 'Cloud Provider'
}
];
export const credentialActionList = [
{
key: 'edit',
@@ -9,7 +9,7 @@ export default [
value: ProviderValueMap.Docker,
key: ProviderValueMap.Docker,
icon: React.cloneElement(icons.DockerOutlined, {
style: { color: 'var(--ant-color-primary)' }
style: { color: 'var(--ant-blue-6)' }
}),
group: 'clusters.create.provider.self'
},
@@ -19,7 +19,7 @@ export default [
value: ProviderValueMap.Kubernetes,
key: ProviderValueMap.Kubernetes,
icon: React.cloneElement(icons.KubernetesFilled, {
style: { color: 'var(--ant-color-primary)' }
style: { color: 'var(--ant-blue-6)' }
}),
group: 'clusters.create.provider.self'
},
@@ -30,13 +30,13 @@ export default [
key: ProviderValueMap.DigitalOcean,
icon: React.cloneElement(icons.DigitalOcean, {
style: {
color: 'var(--ant-color-primary)'
color: 'var(--ant-blue-6)'
}
}),
group: 'clusters.create.provider.cloud'
},
{
label: 'Ali Cloud',
label: 'Alibaba Cloud',
locale: false,
disabled: true,
value: ProviderValueMap.AliCloud,
@@ -54,5 +54,15 @@ export default [
icon: icons.TencentCloud,
description: 'Coming soon',
group: 'clusters.create.provider.cloud'
},
{
label: 'AWS',
locale: false,
disabled: true,
value: ProviderValueMap.AWS,
key: ProviderValueMap.AWS,
icon: icons.AWS,
description: 'Comming soon',
group: 'clusters.create.provider.cloud'
}
];
+6 -1
View File
@@ -31,7 +31,12 @@ const addActions = [
locale: false,
key: ProviderValueMap.DigitalOcean,
value: ProviderValueMap.DigitalOcean,
icon: <IconFont type="icon-digitalocean" />
icon: (
<IconFont
type="icon-digitalocean"
style={{ color: 'var(--ant-blue-6)' }}
/>
)
}
];
-23
View File
@@ -59,29 +59,6 @@ const Catalog: React.FC = () => {
const categoryOptions = [...modelCategories.filter((item) => item.value)];
const filterData = useCallback(
(data: { search: string; categories: string }) => {
const { search, categories } = data;
const dataList = cacheData.current.filter((item) => {
if (search && categories) {
return (
_.toLower(item.name).includes(search) &&
item.categories.includes(categories)
);
}
if (search) {
return _.toLower(item.name).includes(_.toLower(search));
}
if (categories) {
return item.categories.includes(categories);
}
return true;
});
return dataList;
},
[cacheData.current]
);
const fetchData = useCallback(
async (query?: any) => {
const searchQuery = {
@@ -1,5 +1,6 @@
import IconFont from '@/components/icon-font';
import hotkeys from '@/config/hotkeys';
import { SearchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Input } from 'antd';
import React, { useRef } from 'react';
@@ -61,6 +62,12 @@ const SearchInput: React.FC<{
e.stopPropagation();
}}
allowClear
suffix={
<SearchOutlined
className="font-size-16"
style={{ color: 'var(--ant-color-text-placeholder)' }}
/>
}
prefix={
<IconFont
className="font-size-16"
@@ -36,7 +36,7 @@ interface SearchInputProps {
modelSource: string;
isDownload?: boolean;
gpuOptions?: any[];
clusterId: number;
clusterId?: number;
setLoadingModel?: (flag: boolean) => void;
onSourceChange?: (source: string) => void;
onSelectModel: (model: any, manul?: boolean) => void;
@@ -547,7 +547,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
options={filterOptions}
size="middle"
placeholder={intl.formatMessage({ id: 'common.input.type' })}
style={{ width: 150 }}
style={{ width: 120 }}
></BaseSelect>
</span>
<PaginationMain
+29 -27
View File
@@ -4,6 +4,7 @@ import { ProviderValueMap } from '@/pages/cluster-management/config';
import { useIntl } from '@umijs/max';
import { debounce } from 'lodash';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import ColumnWrapper from '../../_components/column-wrapper';
import CompatibilityAlert from '../components/compatible-alert';
import ModelCard from '../components/model-card';
@@ -25,6 +26,18 @@ type AddModalProps = {
onCancel: () => void;
};
const ColWrapper = styled.div`
display: flex;
flex: 1;
max-width: 33.33%;
`;
const FormWrapper = styled.div`
display: flex;
flex: 1;
maxwidth: 100%;
`;
const DownloadModel: React.FC<AddModalProps> = (props) => {
const {
title,
@@ -155,31 +168,17 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
<div style={{ display: 'flex', height: '100%' }}>
{SEARCH_SOURCE.includes(props.source) && (
<>
<div
style={{
display: 'flex',
flex: 1,
maxWidth: '33.33%'
}}
>
<ColumnWrapper>
<SearchModel
hasLinuxWorker={hasLinuxWorker}
modelSource={props.source}
onSelectModel={handleOnSelectModel}
isDownload={true}
></SearchModel>
</ColumnWrapper>
<ColWrapper>
<SearchModel
hasLinuxWorker={hasLinuxWorker}
modelSource={props.source}
onSelectModel={handleOnSelectModel}
isDownload={true}
></SearchModel>
<Separator></Separator>
</div>
<div
style={{
display: 'flex',
flex: 1,
maxWidth: '33.33%'
}}
>
<ColumnWrapper>
</ColWrapper>
<ColWrapper>
<ColumnWrapper styles={{ container: { paddingTop: 0 } }}>
<ModelCard
selectedModel={selectedModel}
onCollapse={setCollapsed}
@@ -189,12 +188,15 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
></ModelCard>
</ColumnWrapper>
<Separator></Separator>
</div>
</ColWrapper>
</>
)}
<div style={{ display: 'flex', flex: 1, maxWidth: '100%' }}>
<FormWrapper>
<ColumnWrapper
paddingBottom={50}
styles={{
container: { paddingTop: 0 }
}}
footer={
<>
<CompatibilityAlert
@@ -238,7 +240,7 @@ const DownloadModel: React.FC<AddModalProps> = (props) => {
></TargetForm>
</>
</ColumnWrapper>
</div>
</FormWrapper>
</div>
</GSDrawer>
);
+29 -3
View File
@@ -4,6 +4,7 @@ import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list';
import { PageAction } from '@/config';
import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max';
import { Form, Typography } from 'antd';
import React, { useMemo } from 'react';
@@ -19,6 +20,7 @@ import { FormData } from '../config/types';
const BackendFields: React.FC = () => {
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance();
const {
isGGUF,
@@ -51,7 +53,15 @@ const BackendFields: React.FC = () => {
return (
<>
<Form.Item name="backend" rules={[{ required: true }]}>
<Form.Item
name="backend"
rules={[
{
required: true,
message: getRuleMessage('select', 'models.form.backend')
}
]}
>
<SealSelect
required
onChange={onBackendChange}
@@ -109,7 +119,15 @@ const BackendFields: React.FC = () => {
)}
{backend === backendOptionsMap.custom && (
<>
<Form.Item<FormData> name="image_name" rules={[{ required: true }]}>
<Form.Item<FormData>
name="image_name"
rules={[
{
required: true,
message: getRuleMessage('input', 'backend.imageName')
}
]}
>
<SealInput.Input
required
allowClear
@@ -117,7 +135,15 @@ const BackendFields: React.FC = () => {
label={intl.formatMessage({ id: 'backend.imageName' })}
></SealInput.Input>
</Form.Item>
<Form.Item<FormData> name="run_command" rules={[{ required: true }]}>
<Form.Item<FormData>
name="run_command"
rules={[
{
required: true,
message: getRuleMessage('input', 'backend.runCommand')
}
]}
>
<SealInput.TextArea
required
scaleSize={true}
+6
View File
@@ -63,6 +63,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
const [form] = Form.useForm();
const intl = useIntl();
const [activeKey, setActiveKey] = React.useState<string[]>([]);
const scheduleType = Form.useWatch('scheduleType', form);
const handleSumit = () => {
form.submit();
@@ -112,6 +113,11 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
const handleClusterChange = (value: number) => {
getGPUOptionList({ clusterId: value });
getBackendOptions({ cluster_id: value });
if (scheduleType === ScheduleValueMap.Manual) {
form.setFieldsValue({
gpu_selector: { gpu_ids: [] }
});
}
};
const handleOnValuesChange = async (changedValues: any, allValues: any) => {
+25 -18
View File
@@ -1,4 +1,5 @@
import SealCascader from '@/components/seal-form/seal-cascader';
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import TooltipList from '@/components/tooltip-list';
import useAppUtils from '@/hooks/use-app-utils';
@@ -9,6 +10,7 @@ import GPUCard from '../components/gpu-card';
import { scheduleList, ScheduleValueMap } from '../config';
import { backendOptionsMap } from '../config/backend-parameters';
import { useCatalogFormContext, useFormContext } from '../config/form-context';
import { FormData } from '../config/types';
const scheduleTypeTips = [
{
@@ -54,6 +56,28 @@ const Performance: React.FC = () => {
return (
<>
<Form.Item<FormData>
name="replicas"
rules={[
{
required: true,
message: getRuleMessage('input', 'models.form.replicas')
}
]}
>
<SealInput.Number
style={{ width: '100%' }}
label={intl.formatMessage({
id: 'models.form.replicas'
})}
required
description={intl.formatMessage(
{ id: 'models.form.replicas.tips' },
{ api: `${window.location.origin}/v1` }
)}
min={0}
></SealInput.Number>
</Form.Item>
<Form.Item name="scheduleType">
<SealSelect
onChange={handleScheduleTypeChange}
@@ -73,24 +97,7 @@ const Performance: React.FC = () => {
<Form.Item name={['gpu_selector', 'gpu_count']}>
<SealSelect
label={intl.formatMessage({ id: 'models.form.gpuCount' })}
options={[
{
label: 'Auto',
value: 'auto'
},
{
label: '1',
value: 1
},
{
label: '2',
value: 2
},
{
label: '4',
value: 4
}
]}
options={[]}
></SealSelect>
</Form.Item>
</>
+1 -1
View File
@@ -1,4 +1,4 @@
@padding: 16px 24px;
@padding: 16px 0px;
.h3 {
position: sticky;
+2 -1
View File
@@ -27,7 +27,8 @@ const UserModels: React.FC = () => {
} = useTableFetch<any>({
fetchAPI: queryMyModels,
API: MY_MODELS_API,
watch: false
watch: false,
isInfiniteScroll: true
});
const intl = useIntl();
@@ -1,16 +1,3 @@
.ant-pro-footer-bar {
padding-block: 20px;
padding-bottom: 30px;
border: none;
.ant-pro-footer-bar-right {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
.messageInput {
position: relative;
display: flex;
+2 -1
View File
@@ -62,7 +62,8 @@ export const addWorkerGuide: Record<string, any> = {
--restart=unless-stopped \\
--privileged \\
--net=host \\
-v gpustack-data:/var/lib/gpustack \\
-v /var/run/docker.sock:/var/run/docker.sock \\
-v /var/lib/gpustack:/var/lib/gpustack \\
${params.image} \\
--server-url ${params.server} \\
--registration-token ${params.token} \\