fix: provder disabled click in k8s

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent 913f2f5077
commit c9c9401b0b
8 changed files with 31 additions and 412 deletions
@@ -331,7 +331,7 @@ const ClusterCreate = () => {
dataList={providerList}
onSelect={handleSelectProvider}
clickable={true}
currentProvider={extraData.provider}
current={extraData.provider}
/>
)}
{renderModules()}
@@ -6,7 +6,7 @@ import styled from 'styled-components';
import { ProviderType, ProviderValueMap } from '../config';
import AddWorkerCommand from './add-worker-command';
import RegisterClusterInner from './register-cluster-inner';
import SupportedHardware from './support-hardware';
import SupportedGPUs from './support-gpus';
const Title = styled.div`
font-size: 16px;
@@ -63,7 +63,8 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
registrationInfo
}) => {
const intl = useIntl();
const [currentProvider, setCurrentProvider] = React.useState<string>('cuda');
const [currentSelection, setCurrentSelection] =
React.useState<string>('cuda');
const [workerCommand, setWorkerCommand] = React.useState<Record<string, any>>(
{
label: 'NVIDIA',
@@ -71,8 +72,9 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
}
);
const handleSelectProvider = (provider: string, item: any) => {
setCurrentProvider(provider);
const handleSelectProvider = (value: string, item: any) => {
if (provider !== ProviderValueMap.Docker) return;
setCurrentSelection(value);
setWorkerCommand(item);
};
@@ -81,9 +83,10 @@ const AddWorkerStep: React.FC<AddModalProps> = ({
<Title>
{intl.formatMessage({ id: 'clusters.create.supportedGpu' })}
</Title>
<SupportedHardware
<SupportedGPUs
onSelect={handleSelectProvider}
currentProvider={currentProvider}
current={provider === ProviderValueMap.Docker ? currentSelection : ''}
clickable={provider === ProviderValueMap.Docker}
/>
{provider === ProviderValueMap.Docker && (
<Content>
@@ -1,390 +0,0 @@
import DropdownButtons from '@/components/drop-down-buttons';
import GaugeChart from '@/components/echarts/gauge';
import IconFont from '@/components/icon-font';
import StatusTag from '@/components/status-tag';
import ThemeTag from '@/components/tags-wrapper/theme-tag';
import Card from '@/components/templates/card';
import { PageAction } from '@/config';
import { Card as ACard, Col, Collapse, Row } from 'antd';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { queryClusterToken } from '../apis';
import {
ClusterStatus,
ClusterStatusLabelMap,
ProviderLabelMap,
ProviderValueMap,
clusterActionList
} from '../config';
import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types';
import AddPool from './add-pool';
import RegisterCluster from './register-cluster';
import WorkerPools from './worker-pools';
const Content = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
.chart-wrapper {
flex: 1;
}
`;
const CardWrapper = styled(ACard)`
text-align: center;
box-shadow: none !important;
flex: 1;
.ant-card {
box-shadow: none;
}
.ant-card-body {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 6px 10px;
}
.label {
font-weight: 400;
margin-bottom: 12px;
font-size: var(--font-size-middle);
color: var(--ant-color-text-secondary);
}
.value {
font-weight: 600;
color: var(--ant-color-text);
font-size: var(--font-size-large);
}
`;
const CardBox = styled.div`
display: flex;
gap: 16px;
flex: 1;
`;
const Inner = styled.div`
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
cursor: default;
.title {
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 8px;
.text {
display: flex;
align-items: center;
font-size: var(--font-size-middle);
font-weight: 500;
color: var(--ant-color-text);
}
}
.content {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
}
`;
const CollapseWrapper = styled(Collapse)`
width: 100%;
border: none;
border-radius: 0;
border-top: 1px solid var(--ant-color-split);
background-color: var(--ant-color-fill-quaternary);
.ant-collapse-content {
border-top: 1px solid var(--ant-color-split);
}
.ant-collapse-header {
font-size: var(--font-size-middle);
padding-block: 10px !important;
.ant-collapse-expand-icon {
margin-left: 0 !important;
}
}
table .ant-table-thead tr {
background-color: transparent !important;
th {
border-bottom: 1px solid var(--ant-color-split) !important;
font-weight: 500 !important;
}
}
`;
const SubTitle = styled.div`
font-size: var(--font-size-middle);
font-weight: 500;
color: var(--ant-color-text);
margin-block: 24px 16px;
`;
interface CardProps {
data: ListItem;
onSelect?: (key: string, row: ListItem) => void;
}
const gaugeConfig = {
radius: '100%',
progress: {
show: true,
roundCap: false,
width: 8
},
axisLine: {
roundCap: false,
lineStyle: {
width: 8,
color: [
[0.5, 'rgba(84, 204, 152, 80%)'],
[0.8, 'rgba(250, 173, 20, 80%)'],
[1, 'rgba(255, 77, 79, 80%)']
]
}
}
};
const CardItem: React.FC<CardProps> = (props) => {
const chartHeight = 160;
const { data, onSelect } = props;
const [show, setShow] = React.useState(false);
const [addPoolStatus, setAddPoolStatus] = React.useState({
open: false,
action: PageAction.CREATE,
title: '',
provider: ProviderValueMap.DigitalOcean
});
const [registerClusterStatus, setRegisterClusterStatus] = React.useState<{
open: boolean;
registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
}>({
open: false,
registrationInfo: {
token: '',
image: '',
server_url: '',
cluster_id: 0
}
});
const handleRegisterCluster = async () => {
try {
const info = await queryClusterToken({ id: data.id });
setRegisterClusterStatus({
open: true,
registrationInfo: {
...info,
cluster_id: data.id
}
});
} catch (error) {}
};
// cluster action handler
const handleOnSelect = (key: string) => {
if (key === 'addPool') {
setAddPoolStatus({
open: true,
action: PageAction.CREATE,
title: 'Add Worker Pool',
provider: data.provider
});
return;
}
if (key === 'register_cluster') {
handleRegisterCluster();
return;
}
onSelect?.(key, data);
};
// pool action handler
const handleOnAction = (action: string, record: NodePoolListItem) => {
if (action === 'edit') {
setAddPoolStatus({
open: true,
action: PageAction.CREATE,
title: 'Edit Worker Pool',
provider: data.provider
});
}
};
const actions = useMemo(() => {
return clusterActionList.filter((item) => {
if (item.provider) {
return item.provider === data.provider;
}
return true;
});
}, [data.provider]);
return (
<Card
height={'auto'}
clickable={false}
ghost
footerHolder={
<CollapseWrapper
onChange={() => setShow(!show)}
expandIconPosition="end"
expandIcon={({ isActive }) => (
<IconFont type="icon-down" rotate={isActive ? 0 : -90} />
)}
items={[
{
key: '1',
label: 'More Information',
children: (
<>
<div className="chart-wrapper">
<Row gutter={16} style={{ width: '100%' }}>
<Col span={6}>
<GaugeChart
title="GPU Utilization"
value={85}
height={chartHeight}
gaugeConfig={gaugeConfig}
/>
</Col>
<Col span={6}>
<GaugeChart
title="CPU Utilization"
value={50}
height={chartHeight}
gaugeConfig={gaugeConfig}
/>
</Col>
<Col span={6}>
<GaugeChart
title="RAM Utilization"
value={70}
height={chartHeight}
gaugeConfig={gaugeConfig}
/>
</Col>
<Col span={6}>
<GaugeChart
title="VRAM Utilization"
value={60}
height={chartHeight}
gaugeConfig={gaugeConfig}
/>
</Col>
</Row>
</div>
{data.provider === ProviderValueMap.DigitalOcean && (
<>
<SubTitle>Worker Pools</SubTitle>
<WorkerPools
provider={data.provider}
workerPools={data.worker_pools}
height={show ? 'auto' : 0}
onAction={handleOnAction}
/>
</>
)}
</>
)
}
]}
></CollapseWrapper>
}
>
<Inner>
<div className="title">
<span className="flex-center gap-8">
<span className="text">{data.name}</span>
<ThemeTag color="purple">
{ProviderLabelMap[data.provider]}
</ThemeTag>
<StatusTag
statusValue={{
status: ClusterStatus[data.state],
text: ClusterStatusLabelMap[data.state]
}}
/>
</span>
<span>
<DropdownButtons
items={actions}
onSelect={handleOnSelect}
></DropdownButtons>
</span>
</div>
<Content>
<CardBox>
<CardWrapper bordered={false}>
<div className="label">Workers</div>
<div className="value">
{data.ready_workers} / {data.workers}
</div>
</CardWrapper>
<CardWrapper bordered={false}>
<div className="label">GPUs</div>
<div className="value">{data.gpus}</div>
</CardWrapper>
<CardWrapper bordered={false}>
<div className="label">Deployments</div>
<div className="value">{data.models}</div>
</CardWrapper>
</CardBox>
</Content>
</Inner>
<AddPool
provider={addPoolStatus.provider}
open={addPoolStatus.open}
action={addPoolStatus.action}
title={addPoolStatus.title}
onCancel={() => {
setAddPoolStatus({
open: false,
action: PageAction.CREATE,
title: '',
provider: 'digitalocean'
});
}}
onOk={() => {
setAddPoolStatus({
open: false,
action: addPoolStatus.action,
title: '',
provider: 'digitalocean'
});
}}
></AddPool>
<RegisterCluster
title="Register Cluster"
open={registerClusterStatus.open}
registrationInfo={registerClusterStatus.registrationInfo}
onCancel={() => {
setRegisterClusterStatus({
open: false,
registrationInfo: {
token: '',
image: '',
server_url: '',
cluster_id: 0
}
});
}}
></RegisterCluster>
</Card>
);
};
export default CardItem;
@@ -17,8 +17,8 @@ const metricsMap = {
color: 'rgba(250, 173, 20,.8)'
},
ram: {
label: 'Used',
type: 'Used',
label: 'RAM',
type: 'RAM',
intl: false,
color: 'rgba(114, 46, 209,.8)'
},
@@ -35,8 +35,8 @@ const metricsMap = {
color: 'rgba(84, 204, 152,.8)'
},
vram: {
label: 'Used',
type: 'Used',
label: 'VRAM',
type: 'VRAM',
intl: false,
color: 'rgba(255, 107, 179, 80%)'
}
@@ -178,7 +178,7 @@ const ClusterMetrics = () => {
<Card height={CardHeight} clickable={false} ghost>
<TrendChart
data={detailContent?.history}
metrics={['vram', 'allocated']}
metrics={['vram']}
metricsMap={metricsMap}
title="VRAM"
></TrendChart>
@@ -188,7 +188,7 @@ const ClusterMetrics = () => {
<Card height={CardHeight} clickable={false} ghost>
<TrendChart
data={detailContent?.history}
metrics={['ram', 'allocated']}
metrics={['ram']}
metricsMap={metricsMap}
title="RAM"
></TrendChart>
@@ -34,7 +34,7 @@ const Title = styled.span`
interface ProviderCatalogProps {
onSelect?: (provider: string, item: any) => void;
cols?: number;
currentProvider?: ProviderType | string;
current?: ProviderType | string;
clickable?: boolean;
dataList: {
label: string;
@@ -52,7 +52,7 @@ const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
cols = 3,
dataList,
clickable,
currentProvider
current
}) => {
const intl = useIntl();
@@ -80,7 +80,7 @@ const ProviderCatalog: React.FC<ProviderCatalogProps> = ({
height="80px"
key={action.key}
onClick={() => onSelect?.(action.key as string, action)}
active={currentProvider === action.key}
active={current === action.key}
disabled={action.disabled}
clickable={clickable}
header={
@@ -22,12 +22,14 @@ const ProviderImage = ({ src, showBg }: { src: string; showBg?: boolean }) => {
interface SupportedHardwareProps {
onSelect?: (provider: string, item: any) => void;
currentProvider?: string;
current?: string;
clickable?: boolean;
}
const SupportedHardware: React.FC<SupportedHardwareProps> = ({
onSelect,
currentProvider
clickable,
current
}) => {
const intl = useIntl();
@@ -110,9 +112,9 @@ const SupportedHardware: React.FC<SupportedHardwareProps> = ({
return (
<ProviderCatalog
onSelect={onSelect}
currentProvider={currentProvider}
current={current}
dataList={supportedHardPlatforms}
clickable={true}
clickable={clickable}
cols={4}
/>
);
+5 -1
View File
@@ -128,7 +128,11 @@ const TargetForm: React.FC<TargetFormProps> = forwardRef((props, ref) => {
showSearch
expandTrigger="hover"
multiple={false}
popupClassName="cascader-popup-wrapper gpu-selector"
classNames={{
popup: {
root: 'cascader-popup-wrapper gpu-selector'
}
}}
maxTagCount={1}
label="Worker"
options={workerOptions}
+1 -1
View File
@@ -15,7 +15,7 @@ const renderCardItem = (data: {
const { label, value, bgColor } = data;
return (
<Card
bordered={false}
variant="borderless"
style={{ background: bgColor }}
className={styles['card-body']}
>