fix: catalog size
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
+46
-2
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -191,6 +191,7 @@ export interface CatalogItem {
|
||||
categories: string[];
|
||||
capabilities: string[];
|
||||
size: number;
|
||||
size_unit: string;
|
||||
activated_size: number;
|
||||
licenses: string[];
|
||||
release_date: string;
|
||||
|
||||
Reference in New Issue
Block a user