From 949eded4ee773f8743ef9bc76e5e3d775fd8516c Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 27 Aug 2025 16:41:38 +0800 Subject: [PATCH] refactor: resource table columns --- src/components/card-wrapper/index.less | 2 +- src/components/echarts/config.ts | 1 + src/components/echarts/line-chart.tsx | 23 +- src/components/echarts/types.ts | 17 +- src/components/page-tools/index.tsx | 17 +- .../seal-form/components/label-info.tsx | 6 +- src/config/global.d.ts | 9 +- src/hooks/use-user-settings.ts | 2 - src/locales/en-US/clusters.ts | 14 + src/locales/en-US/common.ts | 4 +- src/locales/ja-JP/clusters.ts | 29 ++ src/locales/ja-JP/common.ts | 6 +- src/locales/ru-RU/clusters.ts | 29 ++ src/locales/ru-RU/common.ts | 6 +- src/locales/zh-CN/clusters.ts | 14 + src/locales/zh-CN/common.ts | 4 +- src/locales/zh-CN/menu.ts | 2 +- .../cluster-management/clusters-copy.tsx | 489 ------------------ src/pages/cluster-management/clusters.tsx | 24 +- .../components/cluster-detail-content.tsx | 114 +++- .../components/cluster-detail-modal.tsx | 4 +- .../components/trend-chart.tsx | 78 +++ src/pages/cluster-management/config/index.ts | 16 +- src/pages/cluster-management/credentials.tsx | 17 +- .../{config => hooks}/use-cluster-columns.tsx | 24 +- .../dashboard/components/system-load.tsx | 41 +- .../components/usage-inner/index.tsx | 16 +- .../llmodels/components/advance-config.tsx | 6 +- .../components/deploy-builtin-modal.tsx | 18 +- .../llmodels/components/deploy-modal.tsx | 27 +- .../llmodels/components/instance-item.tsx | 2 +- src/pages/llmodels/components/model-card.tsx | 4 - .../llmodels/components/search-model.tsx | 2 +- src/pages/llmodels/components/table-list.tsx | 4 +- src/pages/llmodels/download/index.tsx | 63 +-- src/pages/llmodels/download/target-form.tsx | 23 +- .../llmodels/hooks/use-form-initial-values.ts | 39 +- src/pages/profile/index.tsx | 2 +- src/pages/resources/components/gpus.tsx | 173 ++----- .../resources/components/model-files.tsx | 378 +------------- src/pages/resources/components/workers.tsx | 363 +------------ .../resources/hooks/use-files-columns.tsx | 366 +++++++++++++ src/pages/resources/hooks/use-gpu-columns.tsx | 138 +++++ .../resources/hooks/use-worker-columns.tsx | 318 ++++++++++++ 44 files changed, 1390 insertions(+), 1544 deletions(-) create mode 100644 src/locales/en-US/clusters.ts create mode 100644 src/locales/ja-JP/clusters.ts create mode 100644 src/locales/ru-RU/clusters.ts create mode 100644 src/locales/zh-CN/clusters.ts delete mode 100644 src/pages/cluster-management/clusters-copy.tsx create mode 100644 src/pages/cluster-management/components/trend-chart.tsx rename src/pages/cluster-management/{config => hooks}/use-cluster-columns.tsx (74%) create mode 100644 src/pages/resources/hooks/use-files-columns.tsx create mode 100644 src/pages/resources/hooks/use-gpu-columns.tsx create mode 100644 src/pages/resources/hooks/use-worker-columns.tsx diff --git a/src/components/card-wrapper/index.less b/src/components/card-wrapper/index.less index 592db7c4..8671526c 100644 --- a/src/components/card-wrapper/index.less +++ b/src/components/card-wrapper/index.less @@ -2,6 +2,6 @@ border-radius: var(--border-radius-small); background-color: var(--color-white-1); box-shadow: none; - padding: 10px; + padding: 10px 16px; border: 1px solid var(--ant-color-border); } diff --git a/src/components/echarts/config.ts b/src/components/echarts/config.ts index bbddb374..050736fa 100644 --- a/src/components/echarts/config.ts +++ b/src/components/echarts/config.ts @@ -67,6 +67,7 @@ export default function useChartConfig() { const legend = { itemWidth: 8, itemHeight: 8, + itemGap: 12, textStyle: { color: chartColorMap.axislabelColor } diff --git a/src/components/echarts/line-chart.tsx b/src/components/echarts/line-chart.tsx index 1ba39e14..1967d839 100644 --- a/src/components/echarts/line-chart.tsx +++ b/src/components/echarts/line-chart.tsx @@ -16,7 +16,10 @@ const LineChart: React.FC = (props) => { tooltipValueFormatter = null, legendData = [], smooth, - title + title, + legendOptions, + gridOptions, + titleOptions } = props; const { grid, @@ -42,7 +45,10 @@ const LineChart: React.FC = (props) => { title: { text: '' }, - grid, + grid: { + ...grid, + ...gridOptions + }, tooltip: { ...tooltip, formatter(params: any) { @@ -61,6 +67,7 @@ const LineChart: React.FC = (props) => { yAxis, legend: { ...legend, + ...legendOptions, data: legendData.map((item: any) => { return { name: item, @@ -93,6 +100,7 @@ const LineChart: React.FC = (props) => { animation: false, title: { ...titleConfig, + ...titleOptions, text: title }, yAxis: { @@ -109,7 +117,16 @@ const LineChart: React.FC = (props) => { }, series: data }; - }, [seriesData, xAxisData, yAxisName, title, smooth, legendData, options]); + }, [ + seriesData, + xAxisData, + yAxisName, + title, + smooth, + titleOptions, + legendData, + options + ]); return ( <> diff --git a/src/components/echarts/types.ts b/src/components/echarts/types.ts index 88264e34..2103a252 100644 --- a/src/components/echarts/types.ts +++ b/src/components/echarts/types.ts @@ -1,14 +1,29 @@ -import type { LegendComponentOption } from 'echarts/components'; +import type { + LegendComponentOption, + TitleComponentOption +} from 'echarts/components'; export interface ChartProps { seriesData: any[]; showEmpty?: boolean; xAxisData: string[]; legendData?: LegendComponentOption['data']; + legendOptions?: { + [K in keyof LegendComponentOption]?: LegendComponentOption[K]; + }; + gridOptions?: { + left?: string | number; + right?: string | number; + top?: string | number; + bottom?: string | number; + }; labelFormatter?: (val?: any, index?: number) => string; tooltipValueFormatter?: (val: any) => string; height: string | number; width?: string | number; title?: string; + titleOptions?: { + [K in keyof TitleComponentOption]?: TitleComponentOption[K]; + }; value?: number; smooth?: boolean; color?: string; diff --git a/src/components/page-tools/index.tsx b/src/components/page-tools/index.tsx index 48480e3f..e54c2322 100644 --- a/src/components/page-tools/index.tsx +++ b/src/components/page-tools/index.tsx @@ -93,8 +93,8 @@ export const FilterBar: React.FC = (props) => { actionType = 'button', marginBottom = 10, marginTop = 10, - inputHolder = 'common.filter.name', - selectHolder = '', + inputHolder, + selectHolder, showPrimaryButton = true, showDeleteButton = true, width @@ -165,9 +165,12 @@ export const FilterBar: React.FC = (props) => { left={ = (props) => { - - - } - right={ - - - - - - - } - > - - - { - return ( - - {text} - - ); - }} - /> - { - return ( - - {addActions.find((item) => item.value === record.provider) - ?.label || 'N/A'} - - ); - }} - /> - { - return ( - - - - 3 - - - - 1 - - - ); - }} - /> - { - return ( - - {text} - - ); - }} - /> - - { - return ( - - {text} - - ); - }} - /> - { - return ( - handleSelect(val, record)} - > - ); - }} - /> -
-
- - - { - setAddPoolStatus({ - open: false, - action: PageAction.CREATE, - title: '', - provider: 'digitalocean' - }); - }} - onOk={() => { - setAddPoolStatus({ - open: false, - action: PageAction.CREATE, - title: '', - provider: 'digitalocean' - }); - }} - > - setOpen(false)}> - - - ); -}; - -export default Credentials; diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index 45356e87..7569fef7 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -6,6 +6,7 @@ import useTableFetch from '@/hooks/use-table-fetch'; import AddWorker from '@/pages/resources/components/add-worker'; import { PageContainer } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; +import { useMemoizedFn } from 'ahooks'; import { Table, message } from 'antd'; import { useState } from 'react'; import { @@ -23,7 +24,7 @@ import { ClusterFormData as FormData, ClusterListItem as ListItem } from './config/types'; -import useClusterColumns from './config/use-cluster-columns'; +import useClusterColumns from './hooks/use-cluster-columns'; const Credentials: React.FC = () => { const { @@ -112,12 +113,19 @@ const Credentials: React.FC = () => { const handleAddCluster = (value: string) => { const label = ProviderLabelMap[value]; + const clusterLabel = + value === ProviderValueMap.Custom + ? intl.formatMessage({ id: 'clusters.provider.custom' }) + : label; setOpenAddModal({ open: true, action: PageAction.CREATE, currentData: undefined, - title: `Add ${label} Cluster`, + title: intl.formatMessage( + { id: 'clusters.add.cluster' }, + { cluster: clusterLabel } + ), provider: value }); }; @@ -184,7 +192,10 @@ const Credentials: React.FC = () => { open: true, action: PageAction.EDIT, currentData: row, - title: `Edit ${row.name} Cluster`, + title: intl.formatMessage( + { id: 'clusters.edit.cluster' }, + { cluster: row.name } + ), provider: row.provider }); }; @@ -214,7 +225,7 @@ const Credentials: React.FC = () => { } catch (error) {} }; - const handleSelect = (val: any, row: ListItem) => { + const handleSelect = useMemoizedFn((val: any, row: ListItem) => { if (val === 'edit') { handleEditCluster(row); } else if (val === 'delete') { @@ -231,7 +242,7 @@ const Credentials: React.FC = () => { } else if (val === 'register_cluster') { handleRegisterCluster(row); } - }; + }); const columns = useClusterColumns(handleSelect); @@ -254,11 +265,10 @@ const Credentials: React.FC = () => { showSelect={false} showPrimaryButton={true} showDeleteButton={true} - selectHolder="Filter by name" marginBottom={22} marginTop={30} width={{ input: 300 }} - buttonText="Add Cluster" + buttonText={intl.formatMessage({ id: 'clusters.button.add' })} actionType="dropdown" actionItems={addActions} rowSelection={rowSelection} diff --git a/src/pages/cluster-management/components/cluster-detail-content.tsx b/src/pages/cluster-management/components/cluster-detail-content.tsx index 959e724f..b207537d 100644 --- a/src/pages/cluster-management/components/cluster-detail-content.tsx +++ b/src/pages/cluster-management/components/cluster-detail-content.tsx @@ -1,17 +1,47 @@ import GaugeChart from '@/components/echarts/gauge'; +import Card from '@/components/templates/card'; import { PageAction } from '@/config'; import { Col, Row } from 'antd'; +import _ from 'lodash'; 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'; +import TrendChart from './trend-chart'; import WorkerPools from './worker-pools'; +const metricsMap = { + cpu: { + label: 'CPU', + type: 'CPU', + intl: false, + color: 'rgba(250, 173, 20,.8)' + }, + ram: { + label: 'Used', + type: 'Used', + intl: false, + color: 'rgba(114, 46, 209,.8)' + }, + gpu: { + label: 'GPU', + type: 'GPU', + intl: false, + color: 'rgba(84, 204, 152,.8)' + }, + vram: { + label: 'Used', + type: 'Used', + intl: false, + color: 'rgba(255, 107, 179, 80%)' + } +}; + const SubTitle = styled.div` font-size: var(--font-size-middle); - font-weight: 500; + font-weight: 700; color: var(--ant-color-text); margin-block: 24px 16px; `; @@ -40,6 +70,12 @@ const gaugeConfig = { } }; +const formatValue = (value: number) => { + return _.round(value || 0, 1); +}; + +const CardHeight = 336; + const ClusterDetail: React.FC = ({ data }) => { const chartHeight = 160; const [show, setShow] = React.useState(false); @@ -49,7 +85,23 @@ const ClusterDetail: React.FC = ({ data }) => { title: '', provider: ProviderValueMap.DigitalOcean }); - const [detailContent, setDetailContent] = useState>({}); + const [detailContent, setDetailContent] = useState<{ + current: { + cpu: number; + ram: number; + gpu: number; + vram: number; + }; + history: Record; + }>({ + current: { + cpu: 0, + ram: 0, + gpu: 0, + vram: 0 + }, + history: {} + }); // pool action handler const handleOnAction = (action: string, record: NodePoolListItem) => { @@ -71,10 +123,21 @@ const ClusterDetail: React.FC = ({ data }) => { const response = await queryClusterDetail({ cluster_id: data!.id }); - setDetailContent(response); + setDetailContent({ + current: response.system_load?.current, + history: response.system_load?.history + }); // handle response } catch (error) { - setDetailContent({}); + setDetailContent({ + current: { + cpu: 0, + ram: 0, + gpu: 0, + vram: 0 + }, + history: {} + }); } }; @@ -91,7 +154,7 @@ const ClusterDetail: React.FC = ({ data }) => { @@ -99,7 +162,7 @@ const ClusterDetail: React.FC = ({ data }) => { @@ -107,7 +170,7 @@ const ClusterDetail: React.FC = ({ data }) => { @@ -115,13 +178,44 @@ const ClusterDetail: React.FC = ({ data }) => { + System Load + + + + + + + + + + + + + + + {data?.provider === ProviderValueMap.DigitalOcean && ( <> Worker Pools @@ -143,7 +237,7 @@ const ClusterDetail: React.FC = ({ data }) => { open: false, action: PageAction.CREATE, title: '', - provider: 'digitalocean' + provider: ProviderValueMap.DigitalOcean }); }} onOk={() => { @@ -151,7 +245,7 @@ const ClusterDetail: React.FC = ({ data }) => { open: false, action: addPoolStatus.action, title: '', - provider: 'digitalocean' + provider: ProviderValueMap.DigitalOcean }); }} > diff --git a/src/pages/cluster-management/components/cluster-detail-modal.tsx b/src/pages/cluster-management/components/cluster-detail-modal.tsx index c4e9a6c5..9f8a874a 100644 --- a/src/pages/cluster-management/components/cluster-detail-modal.tsx +++ b/src/pages/cluster-management/components/cluster-detail-modal.tsx @@ -20,8 +20,8 @@ const ClusterDetailModal: React.FC = ({ return ( diff --git a/src/pages/cluster-management/components/trend-chart.tsx b/src/pages/cluster-management/components/trend-chart.tsx new file mode 100644 index 00000000..88f1a11f --- /dev/null +++ b/src/pages/cluster-management/components/trend-chart.tsx @@ -0,0 +1,78 @@ +import LineChart from '@/components/echarts/line-chart'; +import { useIntl } from '@umijs/max'; +import dayjs from 'dayjs'; +import _ from 'lodash'; +import { useMemo } from 'react'; + +interface TrendChartProps { + data: any; + metrics: string[]; + metricsMap: Record; + title: string; +} + +const titleOptions = { + left: 0 +}; + +const TrendChart: React.FC = ({ + data, + metrics, + metricsMap, + title +}) => { + const intl = useIntl(); + + const tooltipValueFormatter = (value: any) => { + return !value ? value : `${value}%`; + }; + + const generateData = useMemo(() => { + const legendData: string[] = []; + const xAxisData: string[] = []; + let seriesData: { value: number; time: string; type: string }[] = []; + seriesData = _.map(metrics, (item: string) => { + const itemConfig = _.get(metricsMap, item, {}); + const name = itemConfig.intl + ? intl.formatMessage({ id: itemConfig.label }) + : itemConfig.label; + legendData.push(name); + const itemDataList = _.get(data, item, []); + return { + name: name, + color: itemConfig.color, + data: _.map(itemDataList, (item: any) => { + xAxisData.push(dayjs(item.timestamp * 1000).format('HH:mm:ss')); + return { + time: dayjs(item.timestamp * 1000).format('HH:mm:ss'), + value: _.round(_.get(item, 'value', 0), 1) + }; + }) + }; + }); + return { + seriesData, + legendData, + xAxisData: _.uniq(xAxisData) + }; + }, [data, intl]); + + return ( + <> + + + ); +}; + +export default TrendChart; diff --git a/src/pages/cluster-management/config/index.ts b/src/pages/cluster-management/config/index.ts index 41e15b7d..4beec8e6 100644 --- a/src/pages/cluster-management/config/index.ts +++ b/src/pages/cluster-management/config/index.ts @@ -43,8 +43,8 @@ export const generateRegisterCommand = (params: { export const addActions = [ { - label: 'Custom', - locale: false, + label: 'clusters.provider.custom', + locale: true, value: ProviderValueMap.Custom, key: ProviderValueMap.Custom, icon: icons.Docker @@ -78,23 +78,23 @@ export const clusterActionList = [ }, { key: 'add_worker', - label: 'Add Worker', + label: 'resources.button.create', provider: ProviderValueMap.Custom, - locale: false, + locale: true, icon: icons.Docker }, { key: 'register_cluster', - label: 'Register Cluster', + label: 'clusters.button.register', provider: ProviderValueMap.Kubernetes, - locale: false, + locale: true, icon: icons.KubernetesOutlined }, { key: 'addPool', - label: 'Add Node Pool', + label: 'clusters.button.addNodePool', provider: ProviderValueMap.DigitalOcean, - locale: false, + locale: true, icon: icons.Catalog }, { diff --git a/src/pages/cluster-management/credentials.tsx b/src/pages/cluster-management/credentials.tsx index 05d54a5d..4e9b8264 100644 --- a/src/pages/cluster-management/credentials.tsx +++ b/src/pages/cluster-management/credentials.tsx @@ -103,7 +103,7 @@ const Credentials: React.FC = () => { provider: ProviderValueMap.DigitalOcean, open: true, action: PageAction.CREATE, - title: 'Add Cloud Credential', + title: intl.formatMessage({ id: 'clusters.button.addCredential' }), currentData: undefined }); }; @@ -141,7 +141,10 @@ const Credentials: React.FC = () => { provider: row.provider, open: true, action: PageAction.EDIT, - title: `Edit ${row.name} Credential`, + title: intl.formatMessage( + { id: 'common.buton.edit.item' }, + { name: row.name } + ), currentData: row }); }; @@ -186,7 +189,9 @@ const Credentials: React.FC = () => { showPrimaryButton={true} marginBottom={22} marginTop={30} - buttonText={'Add Cloud Credential'} + buttonText={intl.formatMessage({ + id: 'clusters.button.addCredential' + })} handleDeleteByBatch={handleDeleteBatch} handleSearch={handleSearch} handleInputChange={handleNameChange} @@ -212,7 +217,7 @@ const Credentials: React.FC = () => { }} > { }} /> { }} /> void ): ColumnsType => { + const intl = useIntl(); const setActionsItems = (row: ClusterListItem) => { return clusterActionList.filter((item) => { if (item.provider) { @@ -28,17 +30,17 @@ const useClusterColumns = ( return useMemo(() => { return [ { - title: 'Name', + title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', render: (text: string) => {text} }, { - title: 'Provider', + title: intl.formatMessage({ id: 'clusters.table.provider' }), dataIndex: 'provider', render: (value: string) => {ProviderLabelMap[value]} }, { - title: 'Status', + title: intl.formatMessage({ id: 'common.table.status' }), dataIndex: 'state', render: (value: number) => ( {value} + render: (value: number, record: ClusterListItem) => ( + + {record.ready_workers} / {record.workers} + + ) }, { title: 'GPUs', @@ -60,12 +66,12 @@ const useClusterColumns = ( render: (value: number) => {value} }, { - title: 'Deployments', + title: intl.formatMessage({ id: 'clusters.table.deployments' }), dataIndex: 'models', render: (value: number) => {value} }, { - title: 'Created', + title: intl.formatMessage({ id: 'common.table.createTime' }), dataIndex: 'created_at', width: 180, render: (value: string) => ( @@ -73,7 +79,7 @@ const useClusterColumns = ( ) }, { - title: 'Operations', + title: intl.formatMessage({ id: 'common.table.operation' }), dataIndex: 'operations', render: (value: string, record: ClusterListItem) => ( { const intl = useIntl(); const data = useContext(DashboardContext)?.system_load?.current || {}; - const { size } = useWindowResize(); - const [paddingRight, setPaddingRight] = useState('20px'); - const [smallChartHeight, setSmallChartHeight] = useState(190); - const [largeChartHeight, setLargeChartHeight] = useState(400); - - const height = 400; const chartData = useMemo(() => { return { @@ -37,16 +33,6 @@ const SystemLoad = () => { }; }, [data]); - console.log('SystemLoad data:', chartData); - - useEffect(() => { - if (size.width < breakpoints.xl) { - setPaddingRight('0'); - } else { - setPaddingRight('20px'); - } - }, [size.width]); - return (
@@ -58,22 +44,15 @@ const SystemLoad = () => { } /> - - - + + + - - + + = ({ maxWidth }) => { return (
- - + +
= (props) => { )} - {scheduleType === 'manual' && ( + {/* {scheduleType === 'manual' && ( <> = (props) => { > - )} + )} */} = (props) => { return ( - - {title} - - -
- } + title={title} open={open} onClose={handleCancel} destroyOnClose={true} diff --git a/src/pages/llmodels/components/deploy-modal.tsx b/src/pages/llmodels/components/deploy-modal.tsx index 3691abd8..f912c933 100644 --- a/src/pages/llmodels/components/deploy-modal.tsx +++ b/src/pages/llmodels/components/deploy-modal.tsx @@ -246,21 +246,18 @@ const AddModal: FC = (props) => { categories: getCategory(item) }); - setWarningStatus( - { - show: true, - title: '', - type: 'transition', - message: intl.formatMessage({ id: 'models.form.evaluating' }) - }, - { - override: true - } - ); + let warningStatus: MessageStatus = { + show: true, + title: '', + type: 'transition', + message: intl.formatMessage({ id: 'models.form.evaluating' }) + }; if (item.isGGUF) { - fetchModelFiles(); + warningStatus.type = 'danger'; + warningStatus.message = 'GGUF model is not supported.'; } + setWarningStatus(warningStatus, { override: true }); }; const handleOnSelectModelAfterEvaluate = (item: any, manual?: boolean) => { @@ -531,7 +528,11 @@ const AddModal: FC = (props) => { showOkBtn={!showExtraButton} extra={ showExtraButton && ( -