style: cluster table list

This commit is contained in:
jialin
2025-09-16 11:26:17 +08:00
parent 553ec724c2
commit 84859d1b7c
11 changed files with 217 additions and 76 deletions
+1
View File
@@ -179,6 +179,7 @@ export default function useTableFetch<ListItem>(
}, 5000);
};
// for selection change
const handleQueryChange = (params: any) => {
setQueryParams({
...queryParams,
+5 -3
View File
@@ -1,3 +1,4 @@
import { DASHBOARD_API } from '@/pages/dashboard/apis';
import { request } from '@umijs/max';
import {
ClusterFormData,
@@ -83,9 +84,10 @@ export async function deleteCluster(id: number) {
});
}
export async function queryClusterDetail(id: number) {
return request(`${CLUSTERS_API}/${id}`, {
method: 'GET'
export async function queryClusterDetail(params: { cluster_id: number }) {
return request(`${DASHBOARD_API}`, {
method: 'GET',
params
});
}
+80 -25
View File
@@ -1,7 +1,5 @@
import DeleteModal from '@/components/delete-modal';
import { FilterBar } from '@/components/page-tools';
import CardList from '@/components/templates/card-list';
import CardSkeleton from '@/components/templates/card-skelton';
import { PageAction } from '@/config';
import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch';
@@ -19,20 +17,19 @@ import {
} from './apis';
import AddCluster from './components/add-cluster';
import ClusterDetailModal from './components/cluster-detail-modal';
import ClusterItem from './components/cluster-item';
import RegisterCluster from './components/register-cluster';
import { ProviderLabelMap, ProviderValueMap, addActions } from './config';
import {
ClusterFormData as FormData,
ClusterListItem as ListItem
} from './config/types';
const { Column } = Table;
import useClusterColumns from './config/use-cluster-columns';
const Credentials: React.FC = () => {
const {
dataSource,
rowSelection,
queryParams,
sortOrder,
modalRef,
handleDelete,
handleDeleteBatch,
@@ -48,12 +45,29 @@ const Credentials: React.FC = () => {
});
const intl = useIntl();
const [openClusterDetail, setOpenClusterDetail] = useState<{
const [registerClusterStatus, setRegisterClusterStatus] = useState<{
open: boolean;
id: number;
registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
}>({
open: false,
id: 0
registrationInfo: {
token: '',
image: '',
server_url: '',
cluster_id: 0
}
});
const [openClusterDetail, setOpenClusterDetail] = useState<{
open: boolean;
currentData: ListItem | null;
}>({
open: false,
currentData: {} as ListItem
});
const [openAddWorker, setOpenAddWorker] = useState<{
open: boolean;
@@ -187,6 +201,19 @@ const Credentials: React.FC = () => {
}
};
const handleRegisterCluster = async (row: ListItem) => {
try {
const info = await queryClusterToken({ id: row.id });
setRegisterClusterStatus({
open: true,
registrationInfo: {
...info,
cluster_id: row.id
}
});
} catch (error) {}
};
const handleSelect = (val: any, row: ListItem) => {
if (val === 'edit') {
handleEditCluster(row);
@@ -199,14 +226,14 @@ const Credentials: React.FC = () => {
} else if (val === 'details') {
setOpenClusterDetail({
open: true,
id: row.id
currentData: row
});
} else if (val === 'register_cluster') {
handleRegisterCluster(row);
}
};
const renderCard = (item: ListItem) => {
return <ClusterItem data={item} onSelect={handleSelect}></ClusterItem>;
};
const columns = useClusterColumns(handleSelect);
return (
<>
@@ -230,24 +257,34 @@ const Credentials: React.FC = () => {
selectHolder="Filter by name"
marginBottom={22}
marginTop={30}
handleInputChange={handleNameChange}
handleSearch={handleSearch}
width={{ input: 300 }}
buttonText="Add Cluster"
actionType="dropdown"
actionItems={addActions}
rowSelection={rowSelection}
handleInputChange={handleNameChange}
handleSearch={handleSearch}
handleDeleteByBatch={handleDeleteBatch}
handleClickPrimary={handleClickDropdown}
></FilterBar>
<CardList
dataList={dataSource.dataList}
<Table
rowKey="id"
tableLayout="fixed"
style={{ width: '100%' }}
onChange={handleTableChange}
dataSource={dataSource.dataList}
loading={dataSource.loading}
activeId={-1}
isFirst={!dataSource.loadend}
Skeleton={CardSkeleton}
resizable={false}
defaultSpan={24}
renderItem={renderCard}
></CardList>
rowSelection={rowSelection}
columns={columns}
pagination={{
showSizeChanger: true,
pageSize: queryParams.perPage,
current: queryParams.page,
total: dataSource.total,
hideOnSinglePage: queryParams.perPage === 10,
onChange: handlePageChange
}}
></Table>
</PageContainer>
<AddCluster
provider={openAddModal.provider}
@@ -270,9 +307,27 @@ const Credentials: React.FC = () => {
></AddWorker>
<ClusterDetailModal
open={openClusterDetail.open}
id={openClusterDetail.id}
onClose={() => setOpenClusterDetail({ open: false, id: 0 })}
currentData={openClusterDetail.currentData}
onClose={() =>
setOpenClusterDetail({ open: false, currentData: {} as ListItem })
}
></ClusterDetailModal>
<RegisterCluster
title="Register Cluster"
open={registerClusterStatus.open}
registrationInfo={registerClusterStatus.registrationInfo}
onCancel={() => {
setRegisterClusterStatus({
open: false,
registrationInfo: {
token: '',
image: '',
server_url: '',
cluster_id: 0
}
});
}}
></RegisterCluster>
<DeleteModal ref={modalRef}></DeleteModal>
</>
);
@@ -45,7 +45,6 @@ const AddCluster: React.FC<AddModalProps> = ({
};
const handleOk = async (data: FormData) => {
console.log('handleOk===', data);
onOk({
...data,
provider
@@ -59,17 +58,14 @@ const AddCluster: React.FC<AddModalProps> = ({
const renderAddWorkerContent = () => {
if (provider === ProviderValueMap.Custom) {
return <ContainerInstall token="${token}" />;
return <ContainerInstall />;
}
if (provider === ProviderValueMap.Kubernetes) {
return (
<RegisterClusterInner
data={submissionStatus.data}
></RegisterClusterInner>
);
return <RegisterClusterInner></RegisterClusterInner>;
}
return null;
};
const modalTitle = useMemo(() => {
if (submissionStatus.success) {
return (
@@ -1,8 +1,9 @@
import GaugeChart from '@/components/echarts/gauge';
import { PageAction } from '@/config';
import { Col, Row } from 'antd';
import React from 'react';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { queryClusterDetail } from '../apis';
import { ProviderValueMap } from '../config';
import { ClusterListItem, NodePoolListItem } from '../config/types';
import AddPool from './add-pool';
@@ -16,7 +17,7 @@ const SubTitle = styled.div`
`;
interface ClusterDetailProps {
data: ClusterListItem;
data: ClusterListItem | null;
}
const gaugeConfig = {
@@ -46,8 +47,9 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ data }) => {
open: false,
action: PageAction.CREATE,
title: '',
provider: 'digitalocean'
provider: ProviderValueMap.DigitalOcean
});
const [detailContent, setDetailContent] = useState<Record<string, any>>({});
// pool action handler
const handleOnAction = (action: string, record: NodePoolListItem) => {
@@ -56,11 +58,32 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ data }) => {
open: true,
action: PageAction.CREATE,
title: 'Edit Worker Pool',
provider: data.provider
provider: data!.provider
});
}
};
const getClusterDetail = async () => {
if (!data) {
return;
}
try {
const response = await queryClusterDetail({
cluster_id: data!.id
});
setDetailContent(response);
// handle response
} catch (error) {
setDetailContent({});
}
};
useEffect(() => {
if (data?.id) {
getClusterDetail();
}
}, [data?.id]);
return (
<div>
<div className="chart-wrapper">
@@ -99,12 +122,12 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ data }) => {
</Col>
</Row>
</div>
{data.provider === ProviderValueMap.DigitalOcean && (
{data?.provider === ProviderValueMap.DigitalOcean && (
<>
<SubTitle>Worker Pools</SubTitle>
<WorkerPools
provider={data.provider}
workerPools={data.worker_pools}
provider={data?.provider}
workerPools={data?.worker_pools}
height={show ? 'auto' : 0}
onAction={handleOnAction}
/>
@@ -1,27 +1,19 @@
import GSDrawer from '@/components/scroller-modal/gs-drawer';
import React from 'react';
import { ClusterListItem } from '../config/types';
import ClusterDetailContent from './cluster-detail-content';
interface ClusterDetailModalProps {
open: boolean;
onClose?: () => void;
id: number;
currentData: ClusterListItem | null;
}
const ClusterDetailModal: React.FC<ClusterDetailModalProps> = ({
open,
onClose,
id: clusterId
currentData = {} as ClusterListItem
}) => {
const [data, setData] = React.useState<any>({
id: 2,
name: 'Digital-Ocean-cluster',
provider: 'digitalocean',
workers: 3,
gpus: 6,
status: 'error',
deployments: 2
});
const handleOnClose = () => {
onClose?.();
};
@@ -29,11 +21,11 @@ const ClusterDetailModal: React.FC<ClusterDetailModalProps> = ({
return (
<GSDrawer
width={'80vw'}
title={`Cluster Detail - ${clusterId}`}
title={`Cluster Detail - ${currentData?.name}`}
open={open}
onClose={handleOnClose}
>
<ClusterDetailContent data={data} />
<ClusterDetailContent data={currentData} />
</GSDrawer>
);
};
@@ -14,7 +14,7 @@ import {
ClusterStatusLabelMap,
ProviderLabelMap,
ProviderValueMap,
poolActionList
clusterActionList
} from '../config';
import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types';
import AddPool from './add-pool';
@@ -225,7 +225,7 @@ const CardItem: React.FC<CardProps> = (props) => {
};
const actions = useMemo(() => {
return poolActionList.filter((item) => {
return clusterActionList.filter((item) => {
if (item.provider) {
return item.provider === data.provider;
}
@@ -1,20 +1,7 @@
import HighlightCode from '@/components/highlight-code';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { generateRegisterCommand } from '../config';
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 = {
registrationInfo: {
token: string;
@@ -32,10 +19,6 @@ const AddCluster: React.FC<AddModalProps> = ({ registrationInfo }) => {
});
}, [registrationInfo]);
const applyCommand = useMemo(() => {
return `kubectl apply -f manifest.yaml`;
}, []);
return (
<div>
<HighlightCode
+1 -1
View File
@@ -65,7 +65,7 @@ export const addActions = [
}
];
export const poolActionList = [
export const clusterActionList = [
{
key: 'edit',
label: 'common.button.edit',
@@ -0,0 +1,89 @@
// columns.ts
import AutoTooltip from '@/components/auto-tooltip';
import DropdownButtons from '@/components/drop-down-buttons';
import StatusTag from '@/components/status-tag';
import { ColumnsType } from 'antd/es/table';
import dayjs from 'dayjs';
import { useMemo } from 'react';
import {
ClusterStatus,
ClusterStatusLabelMap,
ProviderLabelMap,
clusterActionList
} from '.';
import { ClusterListItem } from './types';
const useClusterColumns = (
handleSelect: (val: string, record: ClusterListItem) => void
): ColumnsType<ClusterListItem> => {
const setActionsItems = (row: ClusterListItem) => {
return clusterActionList.filter((item) => {
if (item.provider) {
return item.provider === row.provider;
}
return true;
});
};
return useMemo(() => {
return [
{
title: 'Name',
dataIndex: 'name',
render: (text: string) => <AutoTooltip ghost>{text}</AutoTooltip>
},
{
title: 'Provider',
dataIndex: 'provider',
render: (value: string) => <span>{ProviderLabelMap[value]}</span>
},
{
title: 'Status',
dataIndex: 'state',
render: (value: number) => (
<StatusTag
statusValue={{
status: ClusterStatus[value],
text: ClusterStatusLabelMap[value]
}}
/>
)
},
{
title: 'Workers',
dataIndex: 'workers',
render: (value: number) => <span>{value}</span>
},
{
title: 'GPUs',
dataIndex: 'gpus',
render: (value: number) => <span>{value}</span>
},
{
title: 'Deployments',
dataIndex: 'models',
render: (value: number) => <span>{value}</span>
},
{
title: 'Created',
dataIndex: 'created_at',
width: 180,
render: (value: string) => (
<span>{dayjs(value).format('YYYY-MM-DD HH:mm:ss')}</span>
)
},
{
title: 'Operations',
dataIndex: 'operations',
render: (value: string, record: ClusterListItem) => (
<DropdownButtons
items={setActionsItems(record)}
onSelect={(val) => handleSelect(val, record)}
></DropdownButtons>
)
}
];
}, [handleSelect]);
};
export default useClusterColumns;
@@ -133,4 +133,4 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
);
};
export default React.memo(AddWorker);
export default AddWorker;