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); }, 5000);
}; };
// for selection change
const handleQueryChange = (params: any) => { const handleQueryChange = (params: any) => {
setQueryParams({ setQueryParams({
...queryParams, ...queryParams,
+5 -3
View File
@@ -1,3 +1,4 @@
import { DASHBOARD_API } from '@/pages/dashboard/apis';
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { import {
ClusterFormData, ClusterFormData,
@@ -83,9 +84,10 @@ export async function deleteCluster(id: number) {
}); });
} }
export async function queryClusterDetail(id: number) { export async function queryClusterDetail(params: { cluster_id: number }) {
return request(`${CLUSTERS_API}/${id}`, { return request(`${DASHBOARD_API}`, {
method: 'GET' method: 'GET',
params
}); });
} }
+80 -25
View File
@@ -1,7 +1,5 @@
import DeleteModal from '@/components/delete-modal'; import DeleteModal from '@/components/delete-modal';
import { FilterBar } from '@/components/page-tools'; 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 { PageAction } from '@/config';
import type { PageActionType } from '@/config/types'; import type { PageActionType } from '@/config/types';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
@@ -19,20 +17,19 @@ import {
} from './apis'; } from './apis';
import AddCluster from './components/add-cluster'; import AddCluster from './components/add-cluster';
import ClusterDetailModal from './components/cluster-detail-modal'; 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 { ProviderLabelMap, ProviderValueMap, addActions } from './config';
import { import {
ClusterFormData as FormData, ClusterFormData as FormData,
ClusterListItem as ListItem ClusterListItem as ListItem
} from './config/types'; } from './config/types';
const { Column } = Table; import useClusterColumns from './config/use-cluster-columns';
const Credentials: React.FC = () => { const Credentials: React.FC = () => {
const { const {
dataSource, dataSource,
rowSelection, rowSelection,
queryParams, queryParams,
sortOrder,
modalRef, modalRef,
handleDelete, handleDelete,
handleDeleteBatch, handleDeleteBatch,
@@ -48,12 +45,29 @@ const Credentials: React.FC = () => {
}); });
const intl = useIntl(); const intl = useIntl();
const [openClusterDetail, setOpenClusterDetail] = useState<{ const [registerClusterStatus, setRegisterClusterStatus] = useState<{
open: boolean; open: boolean;
id: number; registrationInfo: {
token: string;
image: string;
server_url: string;
cluster_id: number;
};
}>({ }>({
open: false, 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<{ const [openAddWorker, setOpenAddWorker] = useState<{
open: boolean; 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) => { const handleSelect = (val: any, row: ListItem) => {
if (val === 'edit') { if (val === 'edit') {
handleEditCluster(row); handleEditCluster(row);
@@ -199,14 +226,14 @@ const Credentials: React.FC = () => {
} else if (val === 'details') { } else if (val === 'details') {
setOpenClusterDetail({ setOpenClusterDetail({
open: true, open: true,
id: row.id currentData: row
}); });
} else if (val === 'register_cluster') {
handleRegisterCluster(row);
} }
}; };
const renderCard = (item: ListItem) => { const columns = useClusterColumns(handleSelect);
return <ClusterItem data={item} onSelect={handleSelect}></ClusterItem>;
};
return ( return (
<> <>
@@ -230,24 +257,34 @@ const Credentials: React.FC = () => {
selectHolder="Filter by name" selectHolder="Filter by name"
marginBottom={22} marginBottom={22}
marginTop={30} marginTop={30}
handleInputChange={handleNameChange}
handleSearch={handleSearch}
width={{ input: 300 }} width={{ input: 300 }}
buttonText="Add Cluster" buttonText="Add Cluster"
actionType="dropdown" actionType="dropdown"
actionItems={addActions} actionItems={addActions}
rowSelection={rowSelection}
handleInputChange={handleNameChange}
handleSearch={handleSearch}
handleDeleteByBatch={handleDeleteBatch}
handleClickPrimary={handleClickDropdown} handleClickPrimary={handleClickDropdown}
></FilterBar> ></FilterBar>
<CardList <Table
dataList={dataSource.dataList} rowKey="id"
tableLayout="fixed"
style={{ width: '100%' }}
onChange={handleTableChange}
dataSource={dataSource.dataList}
loading={dataSource.loading} loading={dataSource.loading}
activeId={-1} rowSelection={rowSelection}
isFirst={!dataSource.loadend} columns={columns}
Skeleton={CardSkeleton} pagination={{
resizable={false} showSizeChanger: true,
defaultSpan={24} pageSize: queryParams.perPage,
renderItem={renderCard} current: queryParams.page,
></CardList> total: dataSource.total,
hideOnSinglePage: queryParams.perPage === 10,
onChange: handlePageChange
}}
></Table>
</PageContainer> </PageContainer>
<AddCluster <AddCluster
provider={openAddModal.provider} provider={openAddModal.provider}
@@ -270,9 +307,27 @@ const Credentials: React.FC = () => {
></AddWorker> ></AddWorker>
<ClusterDetailModal <ClusterDetailModal
open={openClusterDetail.open} open={openClusterDetail.open}
id={openClusterDetail.id} currentData={openClusterDetail.currentData}
onClose={() => setOpenClusterDetail({ open: false, id: 0 })} onClose={() =>
setOpenClusterDetail({ open: false, currentData: {} as ListItem })
}
></ClusterDetailModal> ></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> <DeleteModal ref={modalRef}></DeleteModal>
</> </>
); );
@@ -45,7 +45,6 @@ const AddCluster: React.FC<AddModalProps> = ({
}; };
const handleOk = async (data: FormData) => { const handleOk = async (data: FormData) => {
console.log('handleOk===', data);
onOk({ onOk({
...data, ...data,
provider provider
@@ -59,17 +58,14 @@ const AddCluster: React.FC<AddModalProps> = ({
const renderAddWorkerContent = () => { const renderAddWorkerContent = () => {
if (provider === ProviderValueMap.Custom) { if (provider === ProviderValueMap.Custom) {
return <ContainerInstall token="${token}" />; return <ContainerInstall />;
} }
if (provider === ProviderValueMap.Kubernetes) { if (provider === ProviderValueMap.Kubernetes) {
return ( return <RegisterClusterInner></RegisterClusterInner>;
<RegisterClusterInner
data={submissionStatus.data}
></RegisterClusterInner>
);
} }
return null; return null;
}; };
const modalTitle = useMemo(() => { const modalTitle = useMemo(() => {
if (submissionStatus.success) { if (submissionStatus.success) {
return ( return (
@@ -1,8 +1,9 @@
import GaugeChart from '@/components/echarts/gauge'; import GaugeChart from '@/components/echarts/gauge';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import { Col, Row } from 'antd'; import { Col, Row } from 'antd';
import React from 'react'; import React, { useEffect, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { queryClusterDetail } from '../apis';
import { ProviderValueMap } from '../config'; import { ProviderValueMap } from '../config';
import { ClusterListItem, NodePoolListItem } from '../config/types'; import { ClusterListItem, NodePoolListItem } from '../config/types';
import AddPool from './add-pool'; import AddPool from './add-pool';
@@ -16,7 +17,7 @@ const SubTitle = styled.div`
`; `;
interface ClusterDetailProps { interface ClusterDetailProps {
data: ClusterListItem; data: ClusterListItem | null;
} }
const gaugeConfig = { const gaugeConfig = {
@@ -46,8 +47,9 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ data }) => {
open: false, open: false,
action: PageAction.CREATE, action: PageAction.CREATE,
title: '', title: '',
provider: 'digitalocean' provider: ProviderValueMap.DigitalOcean
}); });
const [detailContent, setDetailContent] = useState<Record<string, any>>({});
// pool action handler // pool action handler
const handleOnAction = (action: string, record: NodePoolListItem) => { const handleOnAction = (action: string, record: NodePoolListItem) => {
@@ -56,11 +58,32 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ data }) => {
open: true, open: true,
action: PageAction.CREATE, action: PageAction.CREATE,
title: 'Edit Worker Pool', 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 ( return (
<div> <div>
<div className="chart-wrapper"> <div className="chart-wrapper">
@@ -99,12 +122,12 @@ const ClusterDetail: React.FC<ClusterDetailProps> = ({ data }) => {
</Col> </Col>
</Row> </Row>
</div> </div>
{data.provider === ProviderValueMap.DigitalOcean && ( {data?.provider === ProviderValueMap.DigitalOcean && (
<> <>
<SubTitle>Worker Pools</SubTitle> <SubTitle>Worker Pools</SubTitle>
<WorkerPools <WorkerPools
provider={data.provider} provider={data?.provider}
workerPools={data.worker_pools} workerPools={data?.worker_pools}
height={show ? 'auto' : 0} height={show ? 'auto' : 0}
onAction={handleOnAction} onAction={handleOnAction}
/> />
@@ -1,27 +1,19 @@
import GSDrawer from '@/components/scroller-modal/gs-drawer'; import GSDrawer from '@/components/scroller-modal/gs-drawer';
import React from 'react'; import React from 'react';
import { ClusterListItem } from '../config/types';
import ClusterDetailContent from './cluster-detail-content'; import ClusterDetailContent from './cluster-detail-content';
interface ClusterDetailModalProps { interface ClusterDetailModalProps {
open: boolean; open: boolean;
onClose?: () => void; onClose?: () => void;
id: number; currentData: ClusterListItem | null;
} }
const ClusterDetailModal: React.FC<ClusterDetailModalProps> = ({ const ClusterDetailModal: React.FC<ClusterDetailModalProps> = ({
open, open,
onClose, 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 = () => { const handleOnClose = () => {
onClose?.(); onClose?.();
}; };
@@ -29,11 +21,11 @@ const ClusterDetailModal: React.FC<ClusterDetailModalProps> = ({
return ( return (
<GSDrawer <GSDrawer
width={'80vw'} width={'80vw'}
title={`Cluster Detail - ${clusterId}`} title={`Cluster Detail - ${currentData?.name}`}
open={open} open={open}
onClose={handleOnClose} onClose={handleOnClose}
> >
<ClusterDetailContent data={data} /> <ClusterDetailContent data={currentData} />
</GSDrawer> </GSDrawer>
); );
}; };
@@ -14,7 +14,7 @@ import {
ClusterStatusLabelMap, ClusterStatusLabelMap,
ProviderLabelMap, ProviderLabelMap,
ProviderValueMap, ProviderValueMap,
poolActionList clusterActionList
} from '../config'; } from '../config';
import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types'; import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types';
import AddPool from './add-pool'; import AddPool from './add-pool';
@@ -225,7 +225,7 @@ const CardItem: React.FC<CardProps> = (props) => {
}; };
const actions = useMemo(() => { const actions = useMemo(() => {
return poolActionList.filter((item) => { return clusterActionList.filter((item) => {
if (item.provider) { if (item.provider) {
return item.provider === data.provider; return item.provider === data.provider;
} }
@@ -1,20 +1,7 @@
import HighlightCode from '@/components/highlight-code'; import HighlightCode from '@/components/highlight-code';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import styled from 'styled-components';
import { generateRegisterCommand } from '../config'; 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 = { type AddModalProps = {
registrationInfo: { registrationInfo: {
token: string; token: string;
@@ -32,10 +19,6 @@ const AddCluster: React.FC<AddModalProps> = ({ registrationInfo }) => {
}); });
}, [registrationInfo]); }, [registrationInfo]);
const applyCommand = useMemo(() => {
return `kubectl apply -f manifest.yaml`;
}, []);
return ( return (
<div> <div>
<HighlightCode <HighlightCode
+1 -1
View File
@@ -65,7 +65,7 @@ export const addActions = [
} }
]; ];
export const poolActionList = [ export const clusterActionList = [
{ {
key: 'edit', key: 'edit',
label: 'common.button.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;