fix: catalog size

This commit is contained in:
jialin
2026-01-12 12:24:34 +08:00
parent 05ad296b43
commit fc76822bdf
13 changed files with 413 additions and 35 deletions
-7
View File
@@ -1,7 +0,0 @@
.cardWrapper {
border-radius: var(--border-radius-small);
background-color: var(--ant-color-bg-container);
box-shadow: none;
padding: 10px 16px;
border: 1px solid var(--ant-color-border);
}
+10 -6
View File
@@ -1,12 +1,16 @@
import styles from './index.less';
import styled from 'styled-components';
const Wrapper = styled.div`
border-radius: var(--border-radius-small);
background-color: var(--ant-color-bg-container);
box-shadow: none;
padding: 10px 16px;
border: 1px solid var(--ant-color-border);
`;
const CardWrapper = (props: any) => {
const { children, style } = props;
return (
<div className={styles.cardWrapper} style={{ ...style }}>
{children}
</div>
);
return <Wrapper style={{ ...style }}>{children}</Wrapper>;
};
export default CardWrapper;
+3
View File
@@ -84,6 +84,9 @@ export default {
trackHoverBg: '#646464',
dotActiveBorderColor: 'rgba(255,255,255,0.25)',
dotBorderColor: 'rgba(255,255,255,0.25)'
},
Descriptions: {
itemPaddingBottom: 8
}
},
token: {
+3
View File
@@ -83,6 +83,9 @@ export default {
trackHoverBg: '#B4B4B4',
dotActiveBorderColor: 'rgba(0,0,0,0.25)',
dotBorderColor: 'rgba(0,0,0,0.25)'
},
Descriptions: {
itemPaddingBottom: 8
}
},
token: {
@@ -149,6 +149,13 @@ export async function queryClusterDetail(
});
}
export async function queryClusterItem(params: { id: number }, options?: any) {
return request<ClusterListItem>(`${CLUSTERS_API}/${params.id}`, {
method: 'GET',
cancelToken: options?.token
});
}
export async function queryClusterToken(params: { id: number }) {
return request(`${CLUSTERS_API}/${params.id}/${CLUSTER_TOKEN}`, {
method: 'GET'
@@ -1,7 +1,10 @@
import IconFont from '@/components/icon-font';
import WorkerList from '@/pages/resources/components/workers';
import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
import { Tabs } from 'antd';
import { PageContainerInner } from '../_components/page-box';
import PageBreadcrumb from '../_components/page-breadcrumb';
import ClusterBasic from './components/detail/cluster-basic';
import ClusterSystemLoad from './components/detail/cluster-system-load';
const ClusterDetailModal = () => {
@@ -27,13 +30,40 @@ const ClusterDetailModal = () => {
title: <PageBreadcrumb items={breadcrumbItems} />
}}
>
<ClusterBasic clusterId={Number(id)}></ClusterBasic>
<ClusterSystemLoad clusterId={Number(id)}></ClusterSystemLoad>
<WorkerList
clusterId={id}
showAddButton={false}
showSelect={false}
widths={{ input: 360 }}
sourceType="cluster"
<Tabs
type="card"
style={{ marginTop: 24 }}
defaultActiveKey="workers"
items={[
{
key: 'workers',
label: 'Workers',
icon: <IconFont type="icon-resources" />,
children: (
<WorkerList
clusterId={id}
showAddButton={false}
showSelect={false}
widths={{ input: 360 }}
sourceType="cluster"
/>
)
},
{
key: 'deployments',
label: 'Deployments',
icon: <IconFont type="icon-rocket-launch1" />,
children: <div>Deployments Content</div>
},
{
key: 'gpus',
label: 'GPUs',
icon: <IconFont type="icon-gpu1" />,
children: <div>GPUs Content</div>
}
]}
/>
</PageContainerInner>
);
@@ -0,0 +1,167 @@
import CardWrapper from '@/components/card-wrapper';
import IconFont from '@/components/icon-font';
import StatusTag from '@/components/status-tag';
import { StarFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import type { DescriptionsProps } from 'antd';
import { Descriptions, Tooltip } from 'antd';
import dayjs from 'dayjs';
import React, { useEffect } from 'react';
import styled from 'styled-components';
import {
ClusterStatus,
ClusterStatusLabelMap,
ProviderLabelMap
} from '../../config';
import providers from '../../config/providers';
import { useClusterDetail } from '../../services/use-cluster-detail';
const Container = styled.div`
display: flex;
height: 184px;
.left {
padding: 24px 24px;
width: 160px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
.img {
width: 72px;
height: 72px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background-color: var(--ant-blue-1);
.anticon {
font-size: 36px;
}
}
}
.right {
flex: 1;
padding-block: 24px;
padding-inline: 0 24px;
}
`;
const Title = styled.div`
display: flex;
align-items: center;
gap: 8px;
`;
const Resources = styled.div`
display: flex;
gap: 24px;
align-items: center;
line-height: 22px;
margin-top: 16px;
.item {
display: flex;
align-items: center;
gap: 8px;
.anticon {
color: var(--ant-color-text-quaternary);
font-size: 16px;
}
.value {
font-weight: 500;
}
.label {
color: var(--ant-color-text-secondary);
}
}
`;
const ClusterBasic: React.FC<{ clusterId: number }> = ({ clusterId }) => {
const intl = useIntl();
const { clusterDetail, fetchClusterDetail } = useClusterDetail();
const items: DescriptionsProps['items'] = [
{
key: 'provider',
label: intl.formatMessage({ id: 'clusters.table.provider' }),
children: ProviderLabelMap[clusterDetail.provider || '']
},
{
key: 'created_at',
label: intl.formatMessage({ id: 'common.table.createTime' }),
children: dayjs(clusterDetail.created_at).format('YYYY-MM-DD HH:mm:ss')
}
];
useEffect(() => {
if (clusterId) {
fetchClusterDetail({ id: clusterId });
}
}, [clusterId]);
return (
<CardWrapper style={{ padding: 0 }}>
<Container>
<div className="left">
<div className="img">
{
providers.find((item) => item.value === clusterDetail?.provider)
?.icon
}
</div>
<div className="status">
<StatusTag
statusValue={{
status: ClusterStatus[clusterDetail?.state || ''],
text: ClusterStatusLabelMap[clusterDetail?.state || '']
}}
/>
</div>
</div>
<div className="right">
<Descriptions
title={
<Title>
<span>{clusterDetail.name}</span>
{clusterDetail.is_default && (
<Tooltip
title={intl.formatMessage({
id: 'clusters.form.setDefault.tips'
})}
>
<StarFilled
style={{ color: 'var(--ant-gold-4)', marginLeft: 4 }}
/>
</Tooltip>
)}
</Title>
}
layout="vertical"
items={items}
/>
<Resources>
<div className="item">
<IconFont type="icon-resources-filled"></IconFont>
<span className="value">
{clusterDetail.ready_workers}/{clusterDetail.workers}
</span>
<span className="label">Workers</span>
</div>
<div className="item">
<IconFont type="icon-rocket-launch-fill"></IconFont>
<span className="value">{clusterDetail.models}</span>
<span className="label">Deployments</span>
</div>
<div className="item">
<IconFont type="icon-gpu"></IconFont>
<span className="value">{clusterDetail.gpus}</span>
<span className="label">GPUs</span>
</div>
</Resources>
</div>
</Container>
</CardWrapper>
);
};
export default ClusterBasic;
@@ -1,5 +1,36 @@
import CardWrapper from '@/components/card-wrapper';
import { Col, Progress, Row } from 'antd';
import React, { useEffect } from 'react';
import { useClusterSystemLoad } from '../../services/use-cluster-system-load';
import styled from 'styled-components';
import { useClusterSystemLoad } from '../../services/use-cluster-detail';
const Container = styled.div`
display: flex;
flex-direction: column;
height: 86px;
gap: 16px;
.title {
font-size: 14px;
font-weight: 500;
color: var(--ant-color-text-secondary);
}
.value-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
.value {
display: flex;
flex: 1;
gap: 8px;
flex-direction: column;
align-items: flex-start;
justify-content: center;
font-size: 20px;
font-weight: 600;
}
}
`;
const ClusterSystemLoad: React.FC<{ clusterId: number }> = ({ clusterId }) => {
const { systemLoad, fetchClusterSystemLoad } = useClusterSystemLoad();
@@ -10,7 +41,98 @@ const ClusterSystemLoad: React.FC<{ clusterId: number }> = ({ clusterId }) => {
}
}, [clusterId]);
return <div>Cluster System Load Component</div>;
return (
<Row gutter={16} style={{ marginTop: 24 }}>
<Col span={6}>
<CardWrapper>
<Container>
<div className="title">GPU Utilization</div>
<div className="value-wrapper">
<div className="value">
<span>44%</span>
<Progress
percent={44}
strokeWidth={8}
showInfo={false}
></Progress>
</div>
</div>
</Container>
</CardWrapper>
</Col>
<Col span={6}>
<CardWrapper>
<Container>
<div className="title">VRAM Utilization</div>
<div className="value-wrapper">
<div className="value">44%</div>
<div className="chart">
<Progress
percent={44}
type="circle"
size={50}
strokeWidth={8}
showInfo={false}
></Progress>
</div>
</div>
</Container>
</CardWrapper>
</Col>
<Col span={6}>
<CardWrapper>
<Container>
<div className="title">CPU Utilization</div>
<div className="value-wrapper">
<div className="value">
<span>44%</span>
<Progress
percent={44}
steps={10}
size={{
height: 8
}}
styles={{
root: {
width: '100%'
},
track: {
flex: 1
},
body: {
display: 'flex',
width: '100%'
}
}}
showInfo={false}
></Progress>
</div>
</div>
</Container>
</CardWrapper>
</Col>
<Col span={6}>
<CardWrapper>
<Container>
<div className="title">Memo Utilization</div>
<div className="value-wrapper">
<div className="value">
<span>44%</span>
</div>
<div className="chart">
<Progress
percent={44}
type="circle"
size={50}
strokeWidth={8}
></Progress>
</div>
</div>
</Container>
</CardWrapper>
</Col>
</Row>
);
};
export default ClusterSystemLoad;
@@ -57,6 +57,7 @@ export interface ClusterListItem {
worker_config: Record<string, any>;
provider: ProviderType;
credential_id: number;
created_at: string;
zone: string;
region: string;
gpus: number;
@@ -6,7 +6,7 @@ import StatusTag from '@/components/status-tag';
import { tableSorter } from '@/config/settings';
import { StarFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Tooltip, Typography } from 'antd';
import { Tooltip } from 'antd';
import dayjs from 'dayjs';
import { useMemo } from 'react';
import {
@@ -42,9 +42,10 @@ const useClusterColumns = (
render: (text: string, record: ClusterListItem) => (
<>
<AutoTooltip ghost title={text}>
<Typography.Link onClick={() => onCellClick?.(record, 'name')}>
{/* <Typography.Link onClick={() => onCellClick?.(record, 'name')}>
{record.name}
</Typography.Link>
</Typography.Link> */}
{record.name}
</AutoTooltip>
{record.is_default && (
<Tooltip
@@ -2,9 +2,11 @@ import { createAxiosToken } from '@/hooks/use-chunk-request';
import { useRequest } from 'ahooks';
import { CancelTokenSource } from 'axios';
import { useEffect, useRef, useState } from 'react';
import { queryClusterDetail } from '../apis';
import { queryClusterDetail, queryClusterItem } from '../apis';
import { ClusterListItem } from '../config/types';
export const useClusterSystemLoad = () => {
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
const [systemLoad, setSystemLoad] = useState<{
current: {
cpu: number;
@@ -22,7 +24,6 @@ export const useClusterSystemLoad = () => {
},
history: {}
});
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
const {
run: fetchClusterSystemLoad,
@@ -71,3 +72,46 @@ export const useClusterSystemLoad = () => {
fetchClusterSystemLoad
};
};
export const useClusterDetail = () => {
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
const [clusterDetail, setClusterDetail] = useState<ClusterListItem>(
{} as ClusterListItem
);
const {
run: fetchClusterDetail,
loading,
cancel
} = useRequest(
async (params) => {
axiosTokenRef.current?.cancel();
axiosTokenRef.current = createAxiosToken();
return await queryClusterItem(params, {
token: axiosTokenRef.current.token
});
},
{
manual: true,
onSuccess: (response) => {
setClusterDetail(response);
},
onError: () => {
setClusterDetail({} as ClusterListItem);
}
}
);
useEffect(() => {
return () => {
cancel();
axiosTokenRef.current?.cancel();
};
}, []);
return {
clusterDetail,
loading,
fetchClusterDetail
};
};
+11 -9
View File
@@ -129,15 +129,17 @@ const CatalogItem: React.FC<CatalogItemProps> = (props) => {
);
})}
<span className="dot"></span>
<AutoTooltip
style={{
borderRadius: 4
}}
>
{data.activated_size
? `${data.size}B-A${data.activated_size}B`
: `${data.size}B`}
</AutoTooltip>
{data.size > 0 && (
<AutoTooltip
style={{
borderRadius: 4
}}
>
{data.activated_size
? `${data.size}${data.size_unit}-A${data.activated_size}${data.size_unit}`
: `${data.size}${data.size_unit}`}
</AutoTooltip>
)}
</div>
</div>
</div>
+1
View File
@@ -191,6 +191,7 @@ export interface CatalogItem {
categories: string[];
capabilities: string[];
size: number;
size_unit: string;
activated_size: number;
licenses: string[];
release_date: string;