From 55b0a20a4af3259223d177687f5055b7e996256e Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 6 Aug 2025 16:35:36 +0800 Subject: [PATCH] refactor: update scroll modal --- src/components/alert-info/index.tsx | 2 + src/components/echarts/config.ts | 4 +- src/components/echarts/gauge.tsx | 13 +- src/components/echarts/types.ts | 6 + src/components/scroller-modal/index.tsx | 32 ++++- src/pages/cluster-management/apis/index.ts | 122 +++++++++++++++-- .../components/add-cluster.tsx | 1 - .../components/add-credential.tsx | 1 - .../components/add-pool.tsx | 23 +--- .../components/cluster-item.tsx | 67 +++++++-- .../components/register-cluster.tsx | 17 +-- .../components/worker-pools.tsx | 60 +++++++- src/pages/cluster-management/config/types.ts | 4 +- .../components/usage-inner/export-data.tsx | 15 -- src/pages/resources/components/add-worker.tsx | 6 +- .../resources/components/update-labels.tsx | 129 +++++++----------- src/pages/users/components/add-modal.tsx | 1 - 17 files changed, 340 insertions(+), 163 deletions(-) diff --git a/src/components/alert-info/index.tsx b/src/components/alert-info/index.tsx index 6c2bb29d..76892c31 100644 --- a/src/components/alert-info/index.tsx +++ b/src/components/alert-info/index.tsx @@ -28,6 +28,8 @@ const AlertInfo: React.FC = (props) => { } } style={{ + fontWeight: 400, + whiteSpace: 'pre-line', textAlign: 'center', padding: '2px 5px', borderRadius: 'var(--border-radius-base)', diff --git a/src/components/echarts/config.ts b/src/components/echarts/config.ts index 9e5e1f1a..389ed2d1 100644 --- a/src/components/echarts/config.ts +++ b/src/components/echarts/config.ts @@ -151,7 +151,7 @@ export default function useChartConfig() { progress: { show: true, roundCap: false, - width: 12 + width: 10 }, pointer: { length: '80%', @@ -163,7 +163,7 @@ export default function useChartConfig() { axisLine: { roundCap: false, lineStyle: { - width: 12, + width: 10, color: [ [0.5, 'rgba(84, 204, 152, 80%)'], [0.8, 'rgba(250, 173, 20, 80%)'], diff --git a/src/components/echarts/gauge.tsx b/src/components/echarts/gauge.tsx index 7146a856..5e41cacd 100644 --- a/src/components/echarts/gauge.tsx +++ b/src/components/echarts/gauge.tsx @@ -22,13 +22,18 @@ const GaugeChart: React.FC> = ( title: titleConfig, chartColorMap } = useChartConfig(); - const { value, height, width, labelFormatter, title, color } = props; + const { value, height, width, labelFormatter, title, color, gaugeConfig } = + props; if (!value && value !== 0) { return ; } const setDataOptions = () => { const colorValue = color || strokeColorFunc(value); + const combineGaugeConfig = { + ...gaugeItemConfig, + ...gaugeConfig + }; return { title: { ...titleConfig, @@ -38,11 +43,11 @@ const GaugeChart: React.FC> = ( }, series: [ { - ...gaugeItemConfig, + ...combineGaugeConfig, axisLine: { - ...gaugeItemConfig.axisLine, + ...combineGaugeConfig.axisLine, lineStyle: { - ...gaugeItemConfig.axisLine.lineStyle, + ...combineGaugeConfig.axisLine.lineStyle, color: [ [value / 100, colorValue], [1, chartColorMap.gaugeBgColor] diff --git a/src/components/echarts/types.ts b/src/components/echarts/types.ts index 1910832e..88264e34 100644 --- a/src/components/echarts/types.ts +++ b/src/components/echarts/types.ts @@ -13,6 +13,12 @@ export interface ChartProps { smooth?: boolean; color?: string; yAxisName?: string; + gaugeConfig?: { + radius?: string; + center?: string[]; + startAngle?: number; + endAngle?: number; + }; } export interface AreaChartItemProps { diff --git a/src/components/scroller-modal/index.tsx b/src/components/scroller-modal/index.tsx index 3a3a41a8..1c83e926 100644 --- a/src/components/scroller-modal/index.tsx +++ b/src/components/scroller-modal/index.tsx @@ -1,8 +1,9 @@ +import OverlayScroller from '@/components/overlay-scroller'; import useBodyScroll from '@/hooks/use-body-scroll'; import { Modal, type ModalProps } from 'antd'; import React from 'react'; -const ScrollerModal = (props: ModalProps) => { +const ScrollerModal = (props: ModalProps & { maxContentHeight?: number }) => { const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); React.useEffect(() => { @@ -13,7 +14,34 @@ const ScrollerModal = (props: ModalProps) => { } }, [props.open]); - return ; + return ( + + + {props.children} + + + ); }; export default ScrollerModal; diff --git a/src/pages/cluster-management/apis/index.ts b/src/pages/cluster-management/apis/index.ts index 502ebde1..7666c496 100644 --- a/src/pages/cluster-management/apis/index.ts +++ b/src/pages/cluster-management/apis/index.ts @@ -1,24 +1,45 @@ import { request } from '@umijs/max'; -import { FormData } from '../config/types'; +import { + ClusterFormData, + ClusterListItem, + CredentialFormData, + CredentialListItem, + NodePoolFormData, + NodePoolListItem +} from '../config/types'; export const CREDENTIALS_API = '/credentials'; +export const CLUSTERS_API = '/clusters'; + +export const WORKER_POOLS_API = '/worker-pools'; + +export const CLUSTER_TOKEN = 'registration_token'; + +// ===================== Credentials ===================== + export async function queryCredentialList(params: Global.SearchParams) { - // return request>(`${CREDENTIALS_API}`, { - // method: 'GET', - // params - // }); + return request>( + `${CREDENTIALS_API}`, + { + method: 'GET', + params + } + ); } -export async function createCredential(params: { data: FormData }) { +export async function createCredential(params: { data: CredentialFormData }) { return request(`${CREDENTIALS_API}`, { method: 'POST', data: params.data }); } -export async function updateCredential(params: { data: FormData }) { - return request(`${CREDENTIALS_API}/${params.data.id}`, { +export async function updateCredential(params: { + id: number; + data: CredentialFormData; +}) { + return request(`${CREDENTIALS_API}/${params.id}`, { method: 'PUT', data: params.data }); @@ -29,3 +50,88 @@ export async function deleteCredential(id: number) { method: 'DELETE' }); } + +// ===================== Cluster ===================== + +export async function queryClusterList(params: Global.SearchParams) { + return request>(`${CLUSTERS_API}`, { + method: 'GET', + params + }); +} + +export async function createCluster(params: { data: ClusterFormData }) { + return request(`${CLUSTERS_API}`, { + method: 'POST', + data: params.data + }); +} + +export async function updateCluster(params: { + id: number; + data: ClusterFormData; +}) { + return request(`${CLUSTERS_API}/${params.id}`, { + method: 'PUT', + data: params.data + }); +} + +export async function deleteCluster(id: number) { + return request(`${CLUSTERS_API}/${id}`, { + method: 'DELETE' + }); +} + +export async function queryClusterDetail(id: number) { + return request(`${CLUSTERS_API}/${id}`, { + method: 'GET' + }); +} + +export async function queryClusterToken(id: number) { + return request(`${CLUSTERS_API}/${id}/${CLUSTER_TOKEN}`, { + method: 'GET' + }); +} + +// ===================== Worker Pools ===================== + +export async function queryWorkerPools( + clusterId: number, + params?: Global.SearchParams +) { + return request>( + `${CLUSTERS_API}/${clusterId}/${WORKER_POOLS_API}`, + { + method: 'GET', + params + } + ); +} + +export async function createWorkerPool( + clusterId: number, + params: { data: NodePoolFormData } +) { + return request(`${CLUSTERS_API}/${clusterId}/${WORKER_POOLS_API}`, { + method: 'POST', + data: params.data + }); +} + +export async function updateWorkerPool( + clusterId: number, + params: { id: number; data: NodePoolFormData } +) { + return request(`/${WORKER_POOLS_API}/${params.id}`, { + method: 'PUT', + data: params.data + }); +} + +export async function deleteWorkerPool(clusterId: number, id: number) { + return request(`/${WORKER_POOLS_API}/${id}`, { + method: 'DELETE' + }); +} diff --git a/src/pages/cluster-management/components/add-cluster.tsx b/src/pages/cluster-management/components/add-cluster.tsx index 18513b4b..436e48ec 100644 --- a/src/pages/cluster-management/components/add-cluster.tsx +++ b/src/pages/cluster-management/components/add-cluster.tsx @@ -59,7 +59,6 @@ const AddCluster: React.FC = ({ maskClosable={false} keyboard={false} width={600} - styles={{}} footer={ } diff --git a/src/pages/cluster-management/components/add-credential.tsx b/src/pages/cluster-management/components/add-credential.tsx index 56bb0a51..461851d6 100644 --- a/src/pages/cluster-management/components/add-credential.tsx +++ b/src/pages/cluster-management/components/add-credential.tsx @@ -45,7 +45,6 @@ const AddModal: React.FC = ({ maskClosable={false} keyboard={false} width={600} - styles={{}} footer={ = ({ }; return ( - - {title} - - - } + } @@ -179,7 +170,7 @@ const AddCluster: React.FC = ({ > - + ); }; diff --git a/src/pages/cluster-management/components/cluster-item.tsx b/src/pages/cluster-management/components/cluster-item.tsx index b588c8fe..b32ea247 100644 --- a/src/pages/cluster-management/components/cluster-item.tsx +++ b/src/pages/cluster-management/components/cluster-item.tsx @@ -17,7 +17,7 @@ import { ProviderLabelMap, ProviderValueMap } from '../config'; -import { ClusterListItem as ListItem } from '../config/types'; +import { ClusterListItem as ListItem, NodePoolListItem } from '../config/types'; import WorkerPools from './worker-pools'; const CollapseTitle = styled.div` @@ -43,6 +43,7 @@ const Content = styled.div` const CardWrapper = styled(ACard)` text-align: center; box-shadow: none; + flex: 1; .ant-card { box-shadow: none; } @@ -64,6 +65,12 @@ const CardWrapper = styled(ACard)` } `; +const CardBox = styled.div` + display: flex; + gap: 16px; + flex: 0.5; +`; + const actionItems = [ { key: 'edit', @@ -137,15 +144,36 @@ interface CardProps { 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 = (props) => { const { data, onSelect } = props; const [show, setShow] = React.useState(false); const handleOnSelect = (key: string) => { - console.log('Selected action:', key); onSelect?.(key, data); }; + const handleOnAction = (action: string, record: NodePoolListItem) => {}; + const actions = useMemo(() => { return actionItems.filter((item) => { if (item.provider) { @@ -179,7 +207,7 @@ const CardItem: React.FC = (props) => { -
+
Workers
1
@@ -192,20 +220,40 @@ const CardItem: React.FC = (props) => {
Deployments
2
-
+
- + - + - + - +
@@ -224,8 +272,9 @@ const CardItem: React.FC = (props) => { )} diff --git a/src/pages/cluster-management/components/register-cluster.tsx b/src/pages/cluster-management/components/register-cluster.tsx index 6896d709..647eb2b1 100644 --- a/src/pages/cluster-management/components/register-cluster.tsx +++ b/src/pages/cluster-management/components/register-cluster.tsx @@ -2,9 +2,8 @@ import ModalFooter from '@/components/modal-footer'; import ScrollerModal from '@/components/scroller-modal/index'; import SealInput from '@/components/seal-form/seal-input'; import { PageActionType } from '@/config/types'; -import { CloseOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Button, Form } from 'antd'; +import { Form } from 'antd'; import React from 'react'; import { ClusterFormData as FormData, @@ -45,22 +44,14 @@ const AddCluster: React.FC = ({ return ( - {title} - - - } + title={title} open={open} - onClose={onCancel} + onCancel={handleCancel} destroyOnClose={true} - closeIcon={false} + closeIcon={true} maskClosable={false} keyboard={false} width={600} - styles={{}} footer={ } diff --git a/src/pages/cluster-management/components/worker-pools.tsx b/src/pages/cluster-management/components/worker-pools.tsx index 14a13973..2adfd25e 100644 --- a/src/pages/cluster-management/components/worker-pools.tsx +++ b/src/pages/cluster-management/components/worker-pools.tsx @@ -1,6 +1,11 @@ +import DeleteModal from '@/components/delete-modal'; import DropdownButtons from '@/components/drop-down-buttons'; +import useTableFetch from '@/hooks/use-table-fetch'; import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; import { Table } from 'antd'; +import { useMemo } from 'react'; +import { WORKER_POOLS_API, deleteWorkerPool, queryWorkerPools } from '../apis'; +import { NodePoolListItem as ListItem } from '../config/types'; const actionItems = [ { @@ -20,21 +25,50 @@ const actionItems = [ ]; interface WorkerPoolsProps { - dataSource: any[]; + workerPools: ListItem[]; loading?: boolean; provider: string; height?: string | number; - onAction?: (action: string, record: any) => void; + onAction?: (action: string, record: ListItem) => void; } const WorkerPools: React.FC = ({ - dataSource, + workerPools, loading = false, provider, height = 'auto', onAction }) => { - // dataindex: type, replicas, Batchsize, GPU, Memory, CPU, Storage, CreateTime, Operations + const { + dataSource, + rowSelection, + queryParams, + modalRef, + fetchData, + handleDelete, + handleDeleteBatch, + handlePageChange, + handleTableChange, + handleSearch, + handleNameChange, + handleQueryChange + } = useTableFetch({ + fetchAPI: queryWorkerPools, + deleteAPI: deleteWorkerPool, + API: WORKER_POOLS_API, + watch: false, + contentForDelete: 'resources.modelfiles.modelfile' + }); + + const onSelect = (key: string, record: ListItem) => { + if (key === 'delete') { + handleDelete({ ...record, name: record.instance_type }); + } + if (key === 'edit') { + onAction?.(key, record); + } + }; + const columns = [ { title: 'Type', @@ -79,10 +113,10 @@ const WorkerPools: React.FC = ({ { title: 'Operations', key: 'operations', - render: (_, record) => ( + render: (text: string, record: ListItem) => ( onAction?.(key, record)} + onSelect={(key) => onSelect?.(key, record)} /> ) } @@ -90,6 +124,7 @@ const WorkerPools: React.FC = ({ // mock dataSource const mockData = Array.from({ length: 3 }, (_, index) => ({ + id: index + 1, key: index, type: `Type ${index + 1}`, replicas: Math.floor(Math.random() * 10) + 1, @@ -101,15 +136,26 @@ const WorkerPools: React.FC = ({ createTime: new Date().toLocaleDateString() })); + const dataList = useMemo(() => { + if (workerPools && workerPools.length > 0) { + return workerPools; + } + if (dataSource.dataList && dataSource.dataList.length > 0) { + return dataSource.dataList; + } + return mockData; + }, [workerPools, dataSource.dataList]); + return (
+ ); }; diff --git a/src/pages/cluster-management/config/types.ts b/src/pages/cluster-management/config/types.ts index c50dedb6..3fe2ef75 100644 --- a/src/pages/cluster-management/config/types.ts +++ b/src/pages/cluster-management/config/types.ts @@ -1,4 +1,4 @@ -export interface FormData { +export interface CredentialFormData { name: string; provider: string; access_key: string; @@ -7,7 +7,7 @@ export interface FormData { id?: number; } -export interface ListItem { +export interface CredentialListItem { id: number; name: string; provider: string; diff --git a/src/pages/dashboard/components/usage-inner/export-data.tsx b/src/pages/dashboard/components/usage-inner/export-data.tsx index ef1ae0c3..e1f7f8ea 100644 --- a/src/pages/dashboard/components/usage-inner/export-data.tsx +++ b/src/pages/dashboard/components/usage-inner/export-data.tsx @@ -150,21 +150,6 @@ const ExportData: React.FC<{ style={{ top: '10%' }} - styles={{ - content: { - padding: '0px' - }, - header: { - padding: 'var(--ant-modal-content-padding)', - paddingBottom: '0' - }, - body: { - padding: '0 var(--ant-modal-content-padding)' - }, - footer: { - padding: '0 var(--ant-modal-content-padding)' - } - }} footer={ = (props) => { style={{ top: 100 }} - styles={{ - body: { - minHeight: 450 - } - }} + maxContentHeight={450} footer={null} > diff --git a/src/pages/resources/components/update-labels.tsx b/src/pages/resources/components/update-labels.tsx index 6efe851f..b6b8cee9 100644 --- a/src/pages/resources/components/update-labels.tsx +++ b/src/pages/resources/components/update-labels.tsx @@ -2,7 +2,6 @@ import LabelSelector from '@/components/label-selector'; import ModalFooter from '@/components/modal-footer'; import ScrollerModal from '@/components/scroller-modal'; import SealInput from '@/components/seal-form/seal-input'; -import SimpleOverlay from '@/components/simple-overlay'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -48,90 +47,66 @@ const UpdateLabels: React.FC = (props) => { maskClosable={false} keyboard={false} width={600} - styles={{ - content: { - padding: '0px' - }, - header: { - padding: 'var(--ant-modal-content-padding)', - paddingBottom: '0' - }, - body: { - padding: '0' - }, - footer: { - padding: '0 var(--ant-modal-content-padding)' - } - }} + maxContentHeight={550} footer={ } > - -
- name="name"> - - - - name="labels" - rules={[ - () => ({ - validator(rule, value) { - if (_.keys(value).length > 0) { - if (_.some(_.keys(value), (k: string) => !value[k])) { - return Promise.reject( - intl.formatMessage( - { - id: 'common.validate.value' - }, - { - name: intl.formatMessage({ - id: 'resources.form.label' - }) - } - ) - ); - } + name="name"> + + + + name="labels" + rules={[ + () => ({ + validator(rule, value) { + if (_.keys(value).length > 0) { + if (_.some(_.keys(value), (k: string) => !value[k])) { + return Promise.reject( + intl.formatMessage( + { + id: 'common.validate.value' + }, + { + name: intl.formatMessage({ + id: 'resources.form.label' + }) + } + ) + ); } - return Promise.resolve(); } - }) - ]} - > - - - -
+ return Promise.resolve(); + } + }) + ]} + > + + + ); }; diff --git a/src/pages/users/components/add-modal.tsx b/src/pages/users/components/add-modal.tsx index 6ab859a4..95a9a012 100644 --- a/src/pages/users/components/add-modal.tsx +++ b/src/pages/users/components/add-modal.tsx @@ -63,7 +63,6 @@ const AddModal: React.FC = ({ maskClosable={false} keyboard={false} width={600} - styles={{}} footer={ }