fix: gpu selector by cluster

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent 207c90e26c
commit 553ec724c2
40 changed files with 1106 additions and 1233 deletions
+4 -4
View File
@@ -8,13 +8,13 @@ import {
NodePoolListItem
} from '../config/types';
export const CREDENTIALS_API = '/credentials';
export const CREDENTIALS_API = '/cloud-credentials';
export const CLUSTERS_API = '/clusters';
export const WORKER_POOLS_API = '/worker-pools';
export const CLUSTER_TOKEN = 'registration_token';
export const CLUSTER_TOKEN = 'registration-token';
// ===================== Credentials =====================
@@ -89,8 +89,8 @@ export async function queryClusterDetail(id: number) {
});
}
export async function queryClusterToken(id: number) {
return request(`${CLUSTERS_API}/${id}/${CLUSTER_TOKEN}`, {
export async function queryClusterToken(params: { id: number }) {
return request(`${CLUSTERS_API}/${params.id}/${CLUSTER_TOKEN}`, {
method: 'GET'
});
}
+93 -111
View File
@@ -1,5 +1,4 @@
import DeleteModal from '@/components/delete-modal';
import IconFont from '@/components/icon-font';
import { FilterBar } from '@/components/page-tools';
import CardList from '@/components/templates/card-list';
import CardSkeleton from '@/components/templates/card-skelton';
@@ -7,12 +6,6 @@ import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch';
import AddWorker from '@/pages/resources/components/add-worker';
import {
DeleteOutlined,
EditOutlined,
KubernetesOutlined,
ProfileOutlined
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Table, message } from 'antd';
@@ -21,76 +14,19 @@ import {
createCluster,
deleteCluster,
queryClusterList,
queryClusterToken,
updateCluster
} from './apis';
import AddCluster from './components/add-cluster';
import ClusterDetailModal from './components/cluster-detail-modal';
import ClusterItem from './components/cluster-item';
import { ClusterDataList } from './config';
import { ProviderLabelMap, ProviderValueMap, addActions } from './config';
import {
ClusterFormData as FormData,
ClusterListItem as ListItem
} from './config/types';
const { Column } = Table;
const addActions = [
{
label: 'Custom',
locale: false,
value: 'custom',
key: 'custom',
icon: <IconFont type="icon-docker" className="size-16" />
},
{
label: 'Kubernetes',
locale: false,
value: 'kubernetes',
key: 'kubernetes',
icon: <KubernetesOutlined className="size-16" />
},
{
label: 'Digital Ocean',
locale: false,
value: 'digitalocean',
key: 'digitalocean',
icon: <IconFont type="icon-digitalocean" />
}
];
const ActionList = [
{
key: 'edit',
label: 'common.button.edit',
icon: <EditOutlined></EditOutlined>
},
{
key: 'add',
label: 'Add Worker',
locale: false,
icon: <EditOutlined></EditOutlined>
},
{
key: 'terminal',
label: 'common.button.detail',
icon: <ProfileOutlined />
},
{
key: 'addPool',
label: 'Add Node Pool',
locale: false,
icon: <IconFont type="icon-catalog1" />
},
{
key: 'delete',
props: {
danger: true
},
label: 'common.button.delete',
icon: <DeleteOutlined></DeleteOutlined>
}
];
const Credentials: React.FC = () => {
const {
dataSource,
@@ -121,15 +57,33 @@ const Credentials: React.FC = () => {
});
const [openAddWorker, setOpenAddWorker] = useState<{
open: boolean;
token: string;
registrationInfo: {
token: string;
image: string;
server_url: string;
};
}>({
open: false,
token: ''
registrationInfo: {
token: '',
image: '',
server_url: ''
}
});
const [openAddModal, setOpenAddModal] = useState(false);
const [provider, setProvider] = useState<string>('custom');
const [action, setAction] = useState<PageActionType>(PageAction.CREATE);
const [title, setTitle] = useState<string>('');
const [openAddModal, setOpenAddModal] = useState<{
open: boolean;
action: PageActionType;
currentData?: ListItem;
title: string;
provider: string;
}>({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
const [addPoolStatus, setAddPoolStatus] = useState<{
open: boolean;
action: PageActionType;
@@ -139,25 +93,19 @@ const Credentials: React.FC = () => {
open: false,
action: PageAction.CREATE,
title: '',
provider: 'digitalocean'
provider: ProviderValueMap.DigitalOcean
});
const [currentData, setCurrentData] = useState<ListItem | undefined>(
undefined
);
const setActions = (row: ListItem) => {
if (row.provider !== 'custom') {
return ActionList.filter((item) => item.key !== 'add');
}
return ActionList;
};
const handleAddCluster = (value: string) => {
setOpenAddModal(true);
setAction(PageAction.CREATE);
setProvider(value);
const label = addActions.find((item) => item.value === value)?.label;
setTitle(`Add ${label} Cluster`);
const label = ProviderLabelMap[value];
setOpenAddModal({
open: true,
action: PageAction.CREATE,
currentData: undefined,
title: `Add ${label} Cluster`,
provider: value
});
};
const handleAddPool = (value: string) => {
@@ -178,32 +126,65 @@ const Credentials: React.FC = () => {
...data
};
try {
if (action === PageAction.EDIT) {
if (openAddModal.action === PageAction.EDIT) {
await updateCluster({
data: params,
id: currentData!.id
id: openAddModal.currentData!.id
});
} else {
await createCluster({ data: params });
}
fetchData();
setOpenAddModal(false);
setOpenAddModal({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
message.success(intl.formatMessage({ id: 'common.message.success' }));
} catch (error) {
setOpenAddModal(false);
setOpenAddModal({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
}
};
const handleModalCancel = () => {
console.log('handleModalCancel');
setOpenAddModal(false);
setOpenAddModal({
open: false,
action: PageAction.CREATE,
currentData: undefined,
title: '',
provider: ''
});
};
const handleEditCluster = (row: ListItem) => {
setCurrentData(row);
setOpenAddModal(true);
setAction(PageAction.EDIT);
setTitle(`Edit ${row.name} Cluster`);
setOpenAddModal({
open: true,
action: PageAction.EDIT,
currentData: row,
title: `Edit ${row.name} Cluster`,
provider: row.provider
});
};
const handleAddWorker = async (row: ListItem) => {
try {
const data = await queryClusterToken({ id: row.id });
setOpenAddWorker({
open: true,
registrationInfo: data
});
} catch (error: any) {
message.error(error.message || 'Failed to fetch cluster token');
}
};
const handleSelect = (val: any, row: ListItem) => {
@@ -212,11 +193,7 @@ const Credentials: React.FC = () => {
} else if (val === 'delete') {
handleDelete({ ...row, name: row.name });
} else if (val === 'add_worker') {
setOpenAddWorker({
open: true,
token: '${token}'
});
setCurrentData(row);
handleAddWorker(row);
} else if (val === 'addPool') {
handleAddPool(row.provider);
} else if (val === 'details') {
@@ -255,14 +232,14 @@ const Credentials: React.FC = () => {
marginTop={30}
handleInputChange={handleNameChange}
handleSearch={handleSearch}
width={{ input: 200 }}
width={{ input: 300 }}
buttonText="Add Cluster"
actionType="dropdown"
actionItems={addActions}
handleClickPrimary={handleClickDropdown}
></FilterBar>
<CardList
dataList={ClusterDataList}
dataList={dataSource.dataList}
loading={dataSource.loading}
activeId={-1}
isFirst={!dataSource.loadend}
@@ -273,18 +250,23 @@ const Credentials: React.FC = () => {
></CardList>
</PageContainer>
<AddCluster
provider={provider}
open={openAddModal}
action={action}
title={title}
data={currentData}
provider={openAddModal.provider}
open={openAddModal.open}
action={openAddModal.action}
title={openAddModal.title}
currentData={openAddModal.currentData}
onCancel={handleModalCancel}
onOk={handleModalOk}
></AddCluster>
<AddWorker
open={openAddWorker.open}
onCancel={() => setOpenAddWorker({ open: false, token: '' })}
token={openAddWorker.token}
onCancel={() =>
setOpenAddWorker({
open: false,
registrationInfo: { token: '', image: '', server_url: '' }
})
}
registrationInfo={openAddWorker.registrationInfo}
></AddWorker>
<ClusterDetailModal
open={openClusterDetail.open}
@@ -19,6 +19,7 @@ type AddModalProps = {
title: string;
action: PageActionType;
open: boolean;
currentData?: ListItem; // Used when action is EDIT
provider: string; // 'kubernetes' | 'custom' | 'digitalocean';
onOk: (values: FormData) => void;
onCancel: () => void;
@@ -28,6 +29,7 @@ const AddCluster: React.FC<AddModalProps> = ({
action,
open,
provider,
currentData,
onOk,
onCancel
}) => {
@@ -36,12 +38,20 @@ const AddCluster: React.FC<AddModalProps> = ({
const [submissionStatus, setSubmissionStatus] = React.useState<{
success: boolean;
data: ListItem;
}>({ success: true, data: {} as ListItem });
}>({ success: false, data: {} as ListItem });
const handleSubmit = () => {
form.submit();
};
const handleOk = async (data: FormData) => {
console.log('handleOk===', data);
onOk({
...data,
provider
});
};
const handleCancel = () => {
form.resetFields();
onCancel();
@@ -109,9 +119,14 @@ const AddCluster: React.FC<AddModalProps> = ({
{submissionStatus.success ? (
renderAddWorkerContent()
) : (
<Form form={form} onFinish={onOk} preserve={false}>
<Form
form={form}
onFinish={handleOk}
preserve={false}
initialValues={currentData}
>
<Form.Item<FormData>
name="display_name"
name="name"
rules={[
{
required: true,
@@ -129,7 +144,7 @@ const AddCluster: React.FC<AddModalProps> = ({
required
></SealInput.Input>
</Form.Item>
{provider === 'digitalocean' && (
{provider === ProviderValueMap.DigitalOcean && (
<CloudProvider provider={provider}></CloudProvider>
)}
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
@@ -1,19 +1,23 @@
import ModalFooter from '@/components/modal-footer';
import ScrollerModal from '@/components/scroller-modal';
import SealInput from '@/components/seal-form/seal-input';
import { PageAction, PasswordReg } from '@/config';
import { PageAction } from '@/config';
import { PageActionType } from '@/config/types';
import { useIntl } from '@umijs/max';
import { Button, Form } from 'antd';
import React from 'react';
import { FormData, ListItem } from '../config/types';
import { ProviderValueMap } from '../config';
import {
CredentialFormData as FormData,
CredentialListItem as ListItem
} from '../config/types';
type AddModalProps = {
title: string;
action: PageActionType;
open: boolean;
onOk: (values: FormData) => void;
data?: ListItem;
currentData?: ListItem;
onCancel: () => void;
provider: string; // 'kubernetes' | 'digitalocean';
};
@@ -22,7 +26,7 @@ const AddModal: React.FC<AddModalProps> = ({
action,
open,
onOk,
data,
currentData,
provider,
onCancel
}) => {
@@ -33,6 +37,10 @@ const AddModal: React.FC<AddModalProps> = ({
form.submit();
};
const handleOk = async (data: FormData) => {
onOk(data);
};
return (
<ScrollerModal
title={title}
@@ -53,7 +61,12 @@ const AddModal: React.FC<AddModalProps> = ({
></ModalFooter>
}
>
<Form form={form} onFinish={onOk} preserve={false}>
<Form
form={form}
onFinish={handleOk}
preserve={false}
initialValues={currentData}
>
<Form.Item<FormData>
name="name"
rules={[
@@ -73,14 +86,13 @@ const AddModal: React.FC<AddModalProps> = ({
required
></SealInput.Input>
</Form.Item>
{provider === 'digital_ocean' && (
{provider === ProviderValueMap.DigitalOcean && (
<>
<Form.Item<FormData>
name="access_key"
name="key"
rules={[
{
required: action === PageAction.CREATE,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
@@ -93,11 +105,10 @@ const AddModal: React.FC<AddModalProps> = ({
></SealInput.Password>
</Form.Item>
<Form.Item<FormData>
name="secret_key"
name="secret"
rules={[
{
required: action === PageAction.CREATE,
pattern: PasswordReg,
message: intl.formatMessage({
id: 'users.form.rule.password'
})
@@ -105,17 +116,12 @@ const AddModal: React.FC<AddModalProps> = ({
]}
>
<SealInput.Password
label="Secret Key"
label="Access Secret"
required={action === PageAction.CREATE}
></SealInput.Password>
</Form.Item>
</>
)}
{provider === 'kubernetes' && (
<Form.Item<FormData> name="kubeconfig" rules={[{ required: false }]}>
<SealInput.TextArea label="Kubeconfig"></SealInput.TextArea>
</Form.Item>
)}
<Form.Item<FormData> name="description" rules={[{ required: false }]}>
<SealInput.TextArea
label={intl.formatMessage({ id: 'common.table.description' })}
@@ -30,9 +30,9 @@ const CloudProvider: React.FC<CloudProviderProps> = () => {
label="Credential"
required
options={['credential1', 'credential2', 'credential3'].map(
(item) => ({
(item, i) => ({
label: item,
value: item
value: i
})
)}
></SealSelect>
@@ -5,78 +5,22 @@ 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 {
DeleteOutlined,
EditOutlined,
KubernetesOutlined
} from '@ant-design/icons';
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
ProviderValueMap,
poolActionList
} 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 actionItems = [
{
key: 'edit',
label: 'common.button.edit',
icon: <EditOutlined />
},
{
key: 'details',
label: 'common.button.view',
icon: <IconFont type="icon-detail-info" className="font-size-16" />
},
{
key: 'add_worker',
label: 'Add Worker',
provider: ProviderValueMap.Custom,
locale: false,
icon: <IconFont type="icon-docker" />
},
{
key: 'register_cluster',
label: 'Register Cluster',
provider: ProviderValueMap.Kubernetes,
locale: false,
icon: <KubernetesOutlined />
},
{
key: 'addPool',
label: 'Add Node Pool',
provider: ProviderValueMap.DigitalOcean,
locale: false,
icon: <IconFont type="icon-catalog1" />
},
{
key: 'delete',
label: 'common.button.delete',
icon: <DeleteOutlined />,
props: {
danger: true
}
}
];
const CollapseTitle = styled.div`
display: flex;
align-items: center;
gap: 8px;
font-size: var(--font-size-middle);
font-weight: 500;
color: var(--ant-color-text);
height: 32px;
cursor: pointer;
`;
const Content = styled.div`
display: flex;
justify-content: space-between;
@@ -216,12 +160,39 @@ const CardItem: React.FC<CardProps> = (props) => {
open: false,
action: PageAction.CREATE,
title: '',
provider: 'digitalocean'
provider: ProviderValueMap.DigitalOcean
});
const [registerClusterStatus, setRegisterClusterStatus] = React.useState({
open: false
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') {
@@ -235,9 +206,7 @@ const CardItem: React.FC<CardProps> = (props) => {
}
if (key === 'register_cluster') {
setRegisterClusterStatus({
open: true
});
handleRegisterCluster();
return;
}
onSelect?.(key, data);
@@ -256,7 +225,7 @@ const CardItem: React.FC<CardProps> = (props) => {
};
const actions = useMemo(() => {
return actionItems.filter((item) => {
return poolActionList.filter((item) => {
if (item.provider) {
return item.provider === data.provider;
}
@@ -269,7 +238,7 @@ const CardItem: React.FC<CardProps> = (props) => {
height={'auto'}
clickable={false}
ghost
footer={
footerHolder={
<CollapseWrapper
onChange={() => setShow(!show)}
expandIconPosition="end"
@@ -340,11 +309,13 @@ const CardItem: React.FC<CardProps> = (props) => {
<div className="title">
<span className="flex-center gap-8">
<span className="text">{data.name}</span>
<ThemeTag>{ProviderLabelMap[data.provider]}</ThemeTag>
<ThemeTag color="purple">
{ProviderLabelMap[data.provider]}
</ThemeTag>
<StatusTag
statusValue={{
status: ClusterStatus[data.status],
text: ClusterStatusLabelMap[data.status]
status: ClusterStatus[data.state],
text: ClusterStatusLabelMap[data.state]
}}
/>
</span>
@@ -359,15 +330,17 @@ const CardItem: React.FC<CardProps> = (props) => {
<CardBox>
<CardWrapper bordered={false}>
<div className="label">Workers</div>
<div className="value">1/1</div>
<div className="value">
{data.ready_workers} / {data.workers}
</div>
</CardWrapper>
<CardWrapper bordered={false}>
<div className="label">GPUs</div>
<div className="value">12</div>
<div className="value">{data.gpus}</div>
</CardWrapper>
<CardWrapper bordered={false}>
<div className="label">Deployments</div>
<div className="value">2</div>
<div className="value">{data.models}</div>
</CardWrapper>
</CardBox>
</Content>
@@ -397,10 +370,16 @@ const CardItem: React.FC<CardProps> = (props) => {
<RegisterCluster
title="Register Cluster"
open={registerClusterStatus.open}
data={data}
registrationInfo={registerClusterStatus.registrationInfo}
onCancel={() => {
setRegisterClusterStatus({
open: false
open: false,
registrationInfo: {
token: '',
image: '',
server_url: '',
cluster_id: 0
}
});
}}
></RegisterCluster>
@@ -1,18 +1,22 @@
import ScrollerModal from '@/components/scroller-modal/index';
import React from 'react';
import { ClusterListItem as ListItem } from '../config/types';
import RegisterClusterInner from './resiter-cluster-inner';
type AddModalProps = {
title: string;
open: boolean;
data: ListItem;
registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
onCancel: () => void;
};
const AddCluster: React.FC<AddModalProps> = ({
title,
open,
data,
registrationInfo,
onCancel
}) => {
const handleCancel = () => {
@@ -31,7 +35,7 @@ const AddCluster: React.FC<AddModalProps> = ({
width={600}
footer={false}
>
<RegisterClusterInner data={data} />
<RegisterClusterInner registrationInfo={registrationInfo} />
</ScrollerModal>
);
};
@@ -1,41 +1,39 @@
import HighlightCode from '@/components/highlight-code';
import React, { useEffect } from 'react';
import { queryClusterToken } from '../apis';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { generateRegisterCommand } from '../config';
import { ClusterListItem as ListItem } from '../config/types';
const Title = styled.h3`
font-weight: 600;
color: var(--ant-color-text);
margin-bottom: 12px;
margin-top: 12px;
font-size: var(--font-size-normal);
.ant-tag {
color: var(--ant-color-text-secondary);
font-weight: 400;
}
`;
type AddModalProps = {
data: ListItem;
registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
};
const AddCluster: React.FC<AddModalProps> = ({ data }) => {
const [code, setCode] = React.useState<string>('');
const getToken = async () => {
const res = await queryClusterToken(data?.id);
return res.data?.token || '';
};
const AddCluster: React.FC<AddModalProps> = ({ registrationInfo }) => {
const code = useMemo(() => {
return generateRegisterCommand({
server: registrationInfo?.server_url || window.location.origin,
clusterId: registrationInfo?.cluster_id,
registrationToken: registrationInfo?.token
});
}, [registrationInfo]);
const getCode = async () => {
try {
const token = await getToken();
const command = generateRegisterCommand({
server: window.location.origin,
clusterId: data?.id || 0,
registrationToken: token
});
setCode(command);
} catch (error) {
setCode(
generateRegisterCommand({
server: window.location.origin,
clusterId: data?.id || 0,
registrationToken: '{token}'
})
);
}
};
useEffect(() => {
getCode();
const applyCommand = useMemo(() => {
return `kubectl apply -f manifest.yaml`;
}, []);
return (
+79 -41
View File
@@ -1,57 +1,29 @@
import icons from '@/components/icon-font/icons';
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
export const ClusterDataList = [
{
id: 3,
name: 'Custom-cluster',
provider: 'custom',
clusterType: 'Custom',
workers: 4,
gpus: 8,
status: 'ready',
deployments: 3
},
{
id: 1,
name: 'kubernetes-cluster',
provider: 'kubernetes',
clusterType: 'Kubernetes',
workers: 2,
gpus: 4,
status: 'ready',
deployments: 1
},
{
id: 2,
name: 'Digital-Ocean-cluster',
provider: 'digitalocean',
workers: 3,
gpus: 6,
status: 'error',
deployments: 2
}
];
export const ClusterStatusValueMap = {
Ready: 'ready',
Error: 'error'
Provisioning: 0,
Ready: 3,
ProvisionedNotReady: 1
};
export const ClusterStatusLabelMap = {
[ClusterStatusValueMap.Provisioning]: 'Provisioning',
[ClusterStatusValueMap.Ready]: 'Ready',
[ClusterStatusValueMap.Error]: 'Error'
[ClusterStatusValueMap.ProvisionedNotReady]: 'Provisioned Not Ready'
};
export const ClusterStatus: Record<string, StatusType> = {
[ClusterStatusValueMap.Provisioning]: StatusMaps.transitioning,
[ClusterStatusValueMap.Ready]: StatusMaps.success,
[ClusterStatusValueMap.Error]: StatusMaps.error
[ClusterStatusValueMap.ProvisionedNotReady]: StatusMaps.error
};
export const ProviderValueMap = {
Kubernetes: 'kubernetes',
DigitalOcean: 'digitalocean',
Custom: 'custom'
Kubernetes: 'Kubernetes',
DigitalOcean: 'DigitalOcean',
Custom: 'Custom'
};
export const ProviderLabelMap = {
@@ -65,6 +37,72 @@ export const generateRegisterCommand = (params: {
clusterId: number;
registrationToken: string;
}) => {
return `curl -k -L '${params.server}/v2/clusters/${params.clusterId}/manifests' \\
--header 'Authorization: Bearer ${params.registrationToken}'`;
return `curl -k -L '${params.server}/v1/clusters/${params.clusterId}/manifests' \\
--header 'Authorization: Bearer ${params.registrationToken}' | kubectl apply -f -`;
};
export const addActions = [
{
label: 'Custom',
locale: false,
value: ProviderValueMap.Custom,
key: ProviderValueMap.Custom,
icon: icons.Docker
},
{
label: 'Kubernetes',
locale: false,
value: ProviderValueMap.Kubernetes,
key: ProviderValueMap.Kubernetes,
icon: icons.KubernetesOutlined
},
{
label: 'Digital Ocean',
locale: false,
value: ProviderValueMap.DigitalOcean,
key: ProviderValueMap.DigitalOcean,
icon: icons.DigitalOcean
}
];
export const poolActionList = [
{
key: 'edit',
label: 'common.button.edit',
icon: icons.EditOutlined
},
{
key: 'details',
label: 'common.button.view',
icon: icons.DetailInfo
},
{
key: 'add_worker',
label: 'Add Worker',
provider: ProviderValueMap.Custom,
locale: false,
icon: icons.Docker
},
{
key: 'register_cluster',
label: 'Register Cluster',
provider: ProviderValueMap.Kubernetes,
locale: false,
icon: icons.KubernetesOutlined
},
{
key: 'addPool',
label: 'Add Node Pool',
provider: ProviderValueMap.DigitalOcean,
locale: false,
icon: icons.Catalog
},
{
key: 'delete',
label: 'common.button.delete',
icon: icons.DeleteOutlined,
props: {
danger: true
}
}
];
+9 -5
View File
@@ -1,12 +1,14 @@
export interface CredentialFormData {
name: string;
provider: string;
access_key: string;
secret_key: string;
key: string;
secret: string;
description?: string;
id?: number;
}
export type ClusterStatusType = 0 | 1 | 3;
export interface CredentialListItem {
id: number;
name: string;
@@ -19,7 +21,7 @@ export interface CredentialListItem {
}
export interface ClusterFormData {
display_name: string;
name: string;
description: string;
provider: string;
credential_id: number;
@@ -55,9 +57,11 @@ export interface ClusterListItem {
zone: string;
region: string;
gpus: number;
deployments: number;
models: number;
workers: number;
ready_workers: number;
id: number;
status: string;
state: ClusterStatusType;
state_message: string;
worker_pools: NodePoolListItem[];
}
+28 -85
View File
@@ -1,30 +1,19 @@
import AutoTooltip from '@/components/auto-tooltip';
import DeleteModal from '@/components/delete-modal';
import DropDownActions from '@/components/drop-down-actions';
import DropdownButtons from '@/components/drop-down-buttons';
import IconFont from '@/components/icon-font';
import PageTools from '@/components/page-tools';
import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch';
import {
DeleteOutlined,
DownOutlined,
EditOutlined,
KubernetesOutlined,
SyncOutlined
KubernetesOutlined
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import {
Button,
ConfigProvider,
Empty,
Input,
Space,
Table,
message
} from 'antd';
import { ConfigProvider, Empty, Table, message } from 'antd';
import dayjs from 'dayjs';
import { useState } from 'react';
import {
@@ -34,6 +23,7 @@ import {
updateCredential
} from './apis';
import AddModal from './components/add-credential';
import { ProviderValueMap } from './config';
import {
CredentialFormData as FormData,
CredentialListItem as ListItem
@@ -99,26 +89,22 @@ const Credentials: React.FC = () => {
open: boolean;
action: PageActionType;
title: string;
currentData: ListItem | undefined;
}>({
provider: '',
open: false,
action: PageAction.CREATE,
title: ''
title: '',
currentData: undefined
});
const [currentData, setCurrentData] = useState<ListItem | undefined>(
undefined
);
const handleAddCredential = (value: string) => {
const title =
value === 'digital_ocean'
? 'Add Digital Ocean Credential'
: 'Add Kubernetes Credential';
const handleAddCredential = () => {
setOpenModalStatus({
provider: value,
provider: ProviderValueMap.DigitalOcean,
open: true,
action: PageAction.CREATE,
title: title
title: 'Add Cloud Credential',
currentData: undefined
});
};
@@ -132,7 +118,7 @@ const Credentials: React.FC = () => {
data: {
...params
},
id: currentData!.id
id: openModalStatus.currentData!.id
});
} else {
await createCredential({ data: params });
@@ -151,12 +137,12 @@ const Credentials: React.FC = () => {
};
const handleEditUser = (row: ListItem) => {
setCurrentData(row);
setOpenModalStatus({
provider: row.provider,
open: true,
action: PageAction.EDIT,
title: `Edit ${row.provider} Credential`
title: `Edit ${row.name} Credential`,
currentData: row
});
};
@@ -180,11 +166,6 @@ const Credentials: React.FC = () => {
return <div></div>;
};
const handleClickDropdown = (e: any) => {
const value = e.key;
handleAddCredential(value);
};
return (
<>
<PageContainer
@@ -200,58 +181,20 @@ const Credentials: React.FC = () => {
}}
extra={[]}
>
<PageTools
<FilterBar
showSelect={false}
showPrimaryButton={true}
marginBottom={22}
left={
<Space>
<Input
placeholder={intl.formatMessage({ id: 'common.filter.name' })}
style={{ width: 300 }}
allowClear
onChange={handleNameChange}
></Input>
<Button
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
onClick={handleSearch}
icon={<SyncOutlined></SyncOutlined>}
></Button>
</Space>
}
right={
<Space size={20}>
<DropDownActions
menu={{
items: addActions,
onClick: handleClickDropdown
}}
trigger={['hover']}
placement="bottomRight"
>
<Button
icon={<DownOutlined></DownOutlined>}
type="primary"
iconPosition="end"
>
Add Credential
</Button>
</DropDownActions>
<Button
icon={<DeleteOutlined />}
danger
onClick={handleDeleteBatch}
disabled={!rowSelection.selectedRowKeys.length}
>
<span>
{intl?.formatMessage?.({ id: 'common.button.delete' })}
{rowSelection.selectedRowKeys.length > 0 && (
<span>({rowSelection.selectedRowKeys?.length})</span>
)}
</span>
</Button>
</Space>
}
></PageTools>
marginTop={30}
buttonText={'Add Cloud Credential'}
handleDeleteByBatch={handleDeleteBatch}
handleSearch={handleSearch}
handleInputChange={handleNameChange}
handleClickPrimary={handleAddCredential}
rowSelection={rowSelection}
width={{ input: 300 }}
></FilterBar>
<ConfigProvider renderEmpty={renderEmpty}>
<Table
dataSource={dataSource.dataList}
@@ -355,7 +298,7 @@ const Credentials: React.FC = () => {
open={openModalStatus.open}
action={openModalStatus.action}
title={openModalStatus.title}
data={currentData}
currentData={openModalStatus.currentData}
onCancel={handleModalCancel}
onOk={handleModalOk}
></AddModal>