From 632f206228415e0e015329687c6023b9ada103b8 Mon Sep 17 00:00:00 2001 From: jialin Date: Tue, 2 Sep 2025 15:33:34 +0800 Subject: [PATCH] fix: cluster apis --- config/routes.ts | 14 +- src/components/echarts/line-chart.tsx | 31 +-- src/components/echarts/types.ts | 1 + src/components/status-tag/index.less | 1 + src/components/status-tag/index.tsx | 14 +- src/locales/en-US/clusters.ts | 16 +- src/locales/en-US/resources.ts | 3 +- src/locales/ja-JP/clusters.ts | 32 ++- src/locales/ja-JP/resources.ts | 6 +- src/locales/ru-RU/clusters.ts | 30 ++- src/locales/ru-RU/resources.ts | 3 +- src/locales/zh-CN/clusters.ts | 14 +- src/locales/zh-CN/resources.ts | 3 +- src/pages/cluster-management/apis/index.ts | 6 +- .../cluster-management/cluster-detail.tsx | 33 +++ src/pages/cluster-management/clusters.tsx | 27 +-- .../components/add-credential.tsx | 6 +- .../components/add-pool.tsx | 57 +++-- .../components/cloud-provider-form.tsx | 19 +- .../components/cluster-detail-modal.tsx | 2 +- ...detail-content.tsx => cluster-metrics.tsx} | 37 ++-- .../components/trend-chart.tsx | 1 + .../components/worker-pools.tsx | 196 ++++++++---------- src/pages/cluster-management/config/index.ts | 51 +++-- .../hooks/use-cluster-columns.tsx | 29 ++- .../hooks/use-pools-columns.tsx | 108 ++++++++++ src/pages/cluster-management/layout/index.tsx | 20 ++ src/pages/llmodels/components/data-form.tsx | 56 +++-- src/pages/resources/apis/index.ts | 6 + .../components/container-install.tsx | 1 + src/pages/resources/components/workers.tsx | 4 + src/pages/resources/config/index.ts | 53 +++-- src/pages/resources/config/types.ts | 2 + .../resources/hooks/use-worker-columns.tsx | 25 ++- 34 files changed, 578 insertions(+), 329 deletions(-) create mode 100644 src/pages/cluster-management/cluster-detail.tsx rename src/pages/cluster-management/components/{cluster-detail-content.tsx => cluster-metrics.tsx} (85%) create mode 100644 src/pages/cluster-management/hooks/use-pools-columns.tsx create mode 100644 src/pages/cluster-management/layout/index.tsx diff --git a/config/routes.ts b/config/routes.ts index 571250bb..6950525e 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -155,17 +155,27 @@ export default [ routes: [ { path: '/cluster-management', - redirect: '/cluster-management/clusters' + redirect: '/cluster-management/clusters/list' }, { name: 'clusters', - path: '/cluster-management/clusters', + path: '/cluster-management/clusters/list', key: 'clusters', icon: 'icon-cluster2-outline', selectedIcon: 'icon-cluster2-filled', defaultIcon: 'icon-cluster2-outline', component: './cluster-management/clusters' }, + { + name: 'clusterDetail', + path: '/cluster-management/cluster/detail', + key: 'clusterDetail', + icon: 'icon-cluster2-outline', + selectedIcon: 'icon-cluster2-filled', + defaultIcon: 'icon-cluster2-outline', + hideInMenu: true, + component: './cluster-management/cluster-detail' + }, { name: 'credentials', path: '/cluster-management/credentials', diff --git a/src/components/echarts/line-chart.tsx b/src/components/echarts/line-chart.tsx index 1e859956..bf922f8b 100644 --- a/src/components/echarts/line-chart.tsx +++ b/src/components/echarts/line-chart.tsx @@ -22,7 +22,8 @@ const LineChart: React.FC = (props) => { title, legendOptions, gridOptions, - titleOptions + titleOptions, + showArea } = props; const { grid, @@ -100,19 +101,21 @@ const LineChart: React.FC = (props) => { lineStyle: { ...lineItemConfig.lineStyle, color: item.color - } - // areaStyle: { - // color: new LinearGradient(0, 0, 0, 1, [ - // { - // offset: 0, - // color: colors[0] - // }, - // { - // offset: 1, - // color: colors[1] - // } - // ]) - // } + }, + areaStyle: showArea + ? { + color: new LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: colors[0] + }, + { + offset: 1, + color: colors[1] + } + ]) + } + : null }; }); return { diff --git a/src/components/echarts/types.ts b/src/components/echarts/types.ts index 2103a252..44f71cc3 100644 --- a/src/components/echarts/types.ts +++ b/src/components/echarts/types.ts @@ -5,6 +5,7 @@ import type { export interface ChartProps { seriesData: any[]; showEmpty?: boolean; + showArea?: boolean; xAxisData: string[]; legendData?: LegendComponentOption['data']; legendOptions?: { diff --git a/src/components/status-tag/index.less b/src/components/status-tag/index.less index 5c95b518..27ba5924 100644 --- a/src/components/status-tag/index.less +++ b/src/components/status-tag/index.less @@ -15,6 +15,7 @@ display: flex; align-items: center; height: 20px; + gap: 5px; &.err { cursor: default; diff --git a/src/components/status-tag/index.tsx b/src/components/status-tag/index.tsx index b54087a2..c74f642f 100644 --- a/src/components/status-tag/index.tsx +++ b/src/components/status-tag/index.tsx @@ -29,6 +29,7 @@ type StatusTagProps = { download?: { percent: number; }; + suffix?: React.ReactNode; maxTooltipWidth?: number; extra?: React.ReactNode; actions?: { @@ -46,7 +47,7 @@ const StatusTag: React.FC = ({ extra, actions = [], maxTooltipWidth = 250, - type = 'tag' + suffix }) => { const { text, status } = statusValue; @@ -140,6 +141,7 @@ const StatusTag: React.FC = ({ ); }, [statusValue]); + return ( = ({ }} > - - - + {renderContent()} + {suffix && {suffix}} ) : ( - {renderContent()} + + {renderContent()} + {suffix && {suffix}} + )} ); diff --git a/src/locales/en-US/clusters.ts b/src/locales/en-US/clusters.ts index f78c79e5..bf51a283 100644 --- a/src/locales/en-US/clusters.ts +++ b/src/locales/en-US/clusters.ts @@ -10,6 +10,18 @@ export default { 'clusters.edit.cluster': 'Edit {cluster}', 'clusters.provider.custom': 'Custom', 'clusters.button.register': 'Register Cluster', - 'clusters.button.addNodePool': 'Add Node Pool', - 'clusters.button.add.credential': 'Add {provider} Credential' + 'clusters.button.addNodePool': 'Add Worker Pool', + 'clusters.button.add.credential': 'Add {provider} Credential', + 'clusters.credential.title': 'Credential', + 'clusters.credential.token': 'Access Token', + 'clusters.workerpool.region': 'Region', + 'clusters.workerpool.zone': 'Zone', + 'clusters.workerpool.instanceType': 'Instance Type', + 'clusters.workerpool.replicas': 'Replicas', + 'clusters.workerpool.batchSize': 'Batch Size', + 'clusters.workerpool.osImage': 'OS Image', + 'clusters.workerpool.volumes': 'Volumes', + 'clusters.workerpool.format': 'Format', + 'clusters.workerpool.size': 'Size (GiB)', + 'clusters.workerpool.title': 'Worker Pools' }; diff --git a/src/locales/en-US/resources.ts b/src/locales/en-US/resources.ts index 83c2245f..2c49f7a6 100644 --- a/src/locales/en-US/resources.ts +++ b/src/locales/en-US/resources.ts @@ -92,5 +92,6 @@ export default { 'Download and install the installer. Only supported: {versions}.', 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+', 'resource.register.windows.support': 'win 10, win 11', - 'resources.model.instance': 'Model Instance' + 'resources.model.instance': 'Model Instance', + 'resources.worker.download.privatekey': 'Download Private Key' }; diff --git a/src/locales/ja-JP/clusters.ts b/src/locales/ja-JP/clusters.ts index b0233266..2a0c3ffd 100644 --- a/src/locales/ja-JP/clusters.ts +++ b/src/locales/ja-JP/clusters.ts @@ -10,8 +10,20 @@ export default { 'clusters.edit.cluster': 'Edit {cluster}', 'clusters.provider.custom': 'Custom', 'clusters.button.register': 'Register Cluster', - 'clusters.button.addNodePool': 'Add Node Pool', - 'clusters.button.add.credential': 'Add {provider} Credential' + 'clusters.button.addNodePool': 'Add Worker Pool', + 'clusters.button.add.credential': 'Add {provider} Credential', + 'clusters.credential.title': 'Credential', + 'clusters.credential.token': 'Access Token', + 'clusters.workerpool.region': 'Region', + 'clusters.workerpool.zone': 'Zone', + 'clusters.workerpool.instanceType': 'Instance Type', + 'clusters.workerpool.replicas': 'Replicas', + 'clusters.workerpool.batchSize': 'Batch Size', + 'clusters.workerpool.osImage': 'OS Image', + 'clusters.workerpool.volumes': 'Volumes', + 'clusters.workerpool.format': 'Format', + 'clusters.workerpool.size': 'Size (GiB)', + 'clusters.workerpool.title': 'Worker Pools' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -26,6 +38,18 @@ export default { // 9. 'clusters.edit.cluster': 'Edit {cluster}', // 10. 'clusters.provider.custom': 'Custom', // 11. 'clusters.button.register': 'Register Cluster', -// 12. 'clusters.button.addNodePool': 'Add Node Pool', -// 13. 'clusters.button.add.credential': 'Add {provider} Credential' +// 12. 'clusters.button.addNodePool': 'Add Worker Pool', +// 13. 'clusters.button.add.credential': 'Add {provider} Credential', +// 14. 'clusters.credential.token': 'Access Token', +// 15. 'clusters.workerpool.region': 'Region', +// 16. 'clusters.workerpool.zone': 'Zone', +// 17. 'clusters.workerpool.instanceType': 'Instance Type', +// 18. 'clusters.workerpool.replicas': 'Replicas', +// 19. 'clusters.workerpool.batchSize': 'Batch Size', +// 20. 'clusters.workerpool.osImage': 'OS Image', +// 21. 'clusters.workerpool.volumes': 'Volumes', +// 22. 'clusters.workerpool.format': 'Format', +// 23. 'clusters.workerpool.size': 'Size (GiB)', +// 24. 'clusters.credential.title': 'Credential', +// 25. 'clusters.workerpool.title': 'Worker Pools' // ========== End of To-Do List ========== diff --git a/src/locales/ja-JP/resources.ts b/src/locales/ja-JP/resources.ts index 260b5f0d..dc5f9041 100644 --- a/src/locales/ja-JP/resources.ts +++ b/src/locales/ja-JP/resources.ts @@ -93,7 +93,8 @@ export default { 'Download and install the installer. Only supported: {versions}.', 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+', 'resource.register.windows.support': 'win 10, win 11', - 'resources.model.instance': 'Model Instance' + 'resources.model.instance': 'Model Instance', + 'resources.worker.download.privatekey': 'Download Private Key' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -109,5 +110,6 @@ export default { // 10. 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+', // 11. 'resource.register.windows.support': 'win 10, win 11', // 12. 'resources.filter.status': 'Filter by Status', -// 13. 'resources.model.instance': 'Model Instance' +// 13. 'resources.model.instance': 'Model Instance', +// 14. 'resources.worker.download.privatekey': 'Download Private Key' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/clusters.ts b/src/locales/ru-RU/clusters.ts index b0233266..bd31ceb8 100644 --- a/src/locales/ru-RU/clusters.ts +++ b/src/locales/ru-RU/clusters.ts @@ -10,8 +10,20 @@ export default { 'clusters.edit.cluster': 'Edit {cluster}', 'clusters.provider.custom': 'Custom', 'clusters.button.register': 'Register Cluster', - 'clusters.button.addNodePool': 'Add Node Pool', - 'clusters.button.add.credential': 'Add {provider} Credential' + 'clusters.button.addNodePool': 'Add Worker Pool', + 'clusters.button.add.credential': 'Add {provider} Credential', + 'clusters.credential.title': 'Credential', + 'clusters.credential.token': 'Access Token', + 'clusters.workerpool.region': 'Region', + 'clusters.workerpool.zone': 'Zone', + 'clusters.workerpool.instanceType': 'Instance Type', + 'clusters.workerpool.replicas': 'Replicas', + 'clusters.workerpool.batchSize': 'Batch Size', + 'clusters.workerpool.osImage': 'OS Image', + 'clusters.workerpool.volumes': 'Volumes', + 'clusters.workerpool.format': 'Format', + 'clusters.workerpool.size': 'Size (GiB)', + 'clusters.workerpool.title': 'Worker Pools' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -26,6 +38,18 @@ export default { // 9. 'clusters.edit.cluster': 'Edit {cluster}', // 10. 'clusters.provider.custom': 'Custom', // 11. 'clusters.button.register': 'Register Cluster', -// 12. 'clusters.button.addNodePool': 'Add Node Pool', +// 12. 'clusters.button.addNodePool': 'Add Worker Pool', // 13. 'clusters.button.add.credential': 'Add {provider} Credential' +// 14. 'clusters.credential.title': 'Credential', +// 15. 'clusters.credential.token': 'Access Token', +// 16. 'clusters.workerpool.region': 'Region', +// 17. 'clusters.workerpool.zone': 'Zone', +// 18. 'clusters.workerpool.instanceType': 'Instance Type', +// 19. 'clusters.workerpool.replicas': 'Replicas', +// 20. 'clusters.workerpool.batchSize': 'Batch Size', +// 21. 'clusters.workerpool.osImage': 'OS Image', +// 22. 'clusters.workerpool.volumes': 'Volumes', +// 23. 'clusters.workerpool.format': 'Format', +// 24. 'clusters.workerpool.size': 'Size (GiB)', +// 25. 'clusters.workerpool.title': 'Worker Pools' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/resources.ts b/src/locales/ru-RU/resources.ts index ecd48619..6d4ea136 100644 --- a/src/locales/ru-RU/resources.ts +++ b/src/locales/ru-RU/resources.ts @@ -91,7 +91,8 @@ export default { 'Скачайте и установите инсталлятор. Поддерживаемые версии: {versions}.', 'resource.register.maos.support': 'Apple Silicon (серия M), macOS 14+', 'resource.register.windows.support': 'Windows 10, Windows 11', - 'resources.model.instance': 'Модель экземпляра' + 'resources.model.instance': 'Модель экземпляра', + 'resources.worker.download.privatekey': 'Скачать приватный ключ' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== diff --git a/src/locales/zh-CN/clusters.ts b/src/locales/zh-CN/clusters.ts index db7deef4..761ff23e 100644 --- a/src/locales/zh-CN/clusters.ts +++ b/src/locales/zh-CN/clusters.ts @@ -11,5 +11,17 @@ export default { 'clusters.provider.custom': '自定义', 'clusters.button.register': '注册集群', 'clusters.button.addNodePool': '添加节点池', - 'clusters.button.add.credential': '添加 {provider} 凭证' + 'clusters.button.add.credential': '添加 {provider} 凭证', + 'clusters.credential.title': '凭证', + 'clusters.credential.token': '访问令牌', + 'clusters.workerpool.region': '区域', + 'clusters.workerpool.zone': '可用区', + 'clusters.workerpool.instanceType': '实例类型', + 'clusters.workerpool.replicas': '副本数', + 'clusters.workerpool.batchSize': '批大小', + 'clusters.workerpool.osImage': '操作系统镜像', + 'clusters.workerpool.volumes': '存储卷', + 'clusters.workerpool.format': '文件系统格式', + 'clusters.workerpool.size': '容量(GiB)', + 'clusters.workerpool.title': '节点池' }; diff --git a/src/locales/zh-CN/resources.ts b/src/locales/zh-CN/resources.ts index 7b7568d6..b412d60c 100644 --- a/src/locales/zh-CN/resources.ts +++ b/src/locales/zh-CN/resources.ts @@ -90,5 +90,6 @@ export default { '下载并安装安装包,仅支持 {versions}。', 'resource.register.maos.support': 'M 芯片,macOS 14+', 'resource.register.windows.support': 'win 10, win 11', - 'resources.model.instance': '模型实例' + 'resources.model.instance': '模型实例', + 'resources.worker.download.privatekey': '下载私钥' }; diff --git a/src/pages/cluster-management/apis/index.ts b/src/pages/cluster-management/apis/index.ts index f2e574c9..fe59caa3 100644 --- a/src/pages/cluster-management/apis/index.ts +++ b/src/pages/cluster-management/apis/index.ts @@ -84,7 +84,9 @@ export async function deleteCluster(id: number) { }); } -export async function queryClusterDetail(params: { cluster_id: number }) { +export async function queryClusterDetail(params: { + cluster_id: number | string; +}) { return request(`${DASHBOARD_API}`, { method: 'GET', params @@ -113,7 +115,7 @@ export async function queryWorkerPools( export async function createWorkerPool(params: { data: NodePoolFormData; - clusterId: number; + clusterId: number | string; }) { return request(`${CLUSTERS_API}/${params.clusterId}${WORKER_POOLS_API}`, { method: 'POST', diff --git a/src/pages/cluster-management/cluster-detail.tsx b/src/pages/cluster-management/cluster-detail.tsx new file mode 100644 index 00000000..3d80d860 --- /dev/null +++ b/src/pages/cluster-management/cluster-detail.tsx @@ -0,0 +1,33 @@ +import { PageContainer } from '@ant-design/pro-components'; +import { useSearchParams } from '@umijs/max'; +import ClusterMetrics from './components/cluster-metrics'; +import WorkerPools from './components/worker-pools'; +import { ProviderValueMap } from './config'; + +const ClusterDetailModal = () => { + const [searchParams] = useSearchParams(); + const provider = searchParams.get('provider'); + const clusterName = searchParams.get('name'); + return ( + + {clusterName} + + ), + style: { + paddingInline: 'var(--layout-content-header-inlinepadding)' + }, + breadcrumb: {} + }} + extra={[]} + > + + {provider === ProviderValueMap.DigitalOcean && } + + ); +}; + +export default ClusterDetailModal; diff --git a/src/pages/cluster-management/clusters.tsx b/src/pages/cluster-management/clusters.tsx index b9197574..0205c5c8 100644 --- a/src/pages/cluster-management/clusters.tsx +++ b/src/pages/cluster-management/clusters.tsx @@ -5,7 +5,7 @@ import type { PageActionType } from '@/config/types'; 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 { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Table, message } from 'antd'; import { useEffect, useState } from 'react'; @@ -20,7 +20,6 @@ import { } from './apis'; import AddCluster from './components/add-cluster'; import AddPool from './components/add-pool'; -import ClusterDetailModal from './components/cluster-detail-modal'; import RegisterCluster from './components/register-cluster'; import { ProviderLabelMap, ProviderValueMap, addActions } from './config'; import { @@ -49,6 +48,8 @@ const Credentials: React.FC = () => { contentForDelete: 'menu.clusterManagement.clusters' }); + const [searchParams, setSearchParams] = useSearchParams(); + const navigate = useNavigate(); const intl = useIntl(); const [registerClusterStatus, setRegisterClusterStatus] = useState<{ open: boolean; @@ -67,13 +68,7 @@ const Credentials: React.FC = () => { cluster_id: 0 } }); - const [openClusterDetail, setOpenClusterDetail] = useState<{ - open: boolean; - currentData: ListItem | null; - }>({ - open: false, - currentData: {} as ListItem - }); + const [openAddWorker, setOpenAddWorker] = useState<{ open: boolean; registrationInfo: { @@ -144,7 +139,7 @@ const Credentials: React.FC = () => { setAddPoolStatus({ open: true, action: PageAction.CREATE, - title: `Add Node Pool`, + title: intl.formatMessage({ id: 'clusters.button.addNodePool' }), provider: row.provider, clusterId: row.id }); @@ -245,11 +240,6 @@ const Credentials: React.FC = () => { handleAddWorker(row); } else if (val === 'addPool') { handleAddPool(row); - } else if (val === 'details') { - setOpenClusterDetail({ - open: true, - currentData: row - }); } else if (val === 'register_cluster') { handleRegisterCluster(row); } @@ -355,13 +345,6 @@ const Credentials: React.FC = () => { } registrationInfo={openAddWorker.registrationInfo} > - - setOpenClusterDetail({ open: false, currentData: {} as ListItem }) - } - > = ({ }) => { const [form] = Form.useForm(); const intl = useIntl(); + const { getRuleMessage } = useAppUtils(); const handleSumit = () => { form.submit(); @@ -93,12 +95,12 @@ const AddModal: React.FC = ({ rules={[ { required: action === PageAction.CREATE, - message: 'Access Token is required' + message: getRuleMessage('input', 'clusters.credential.token') } ]} > diff --git a/src/pages/cluster-management/components/add-pool.tsx b/src/pages/cluster-management/components/add-pool.tsx index 5a9dbdb8..96cf8f83 100644 --- a/src/pages/cluster-management/components/add-pool.tsx +++ b/src/pages/cluster-management/components/add-pool.tsx @@ -3,7 +3,9 @@ import ModalFooter from '@/components/modal-footer'; import ScrollerModal from '@/components/scroller-modal'; import SealInputNumber from '@/components/seal-form/input-number'; import SealInput from '@/components/seal-form/seal-input'; +import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; +import useAppUtils from '@/hooks/use-app-utils'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -33,6 +35,7 @@ const AddCluster: React.FC = ({ }) => { const [form] = Form.useForm(); const intl = useIntl(); + const { getRuleMessage } = useAppUtils(); const handleSumit = () => { form.submit(); @@ -97,17 +100,17 @@ const AddCluster: React.FC = ({ rules={[ { required: true, - message: intl.formatMessage( - { id: 'common.form.rule.input' }, - { - name: intl.formatMessage({ id: 'common.table.name' }) - } + message: getRuleMessage( + 'input', + 'clusters.workerpool.instanceType' ) } ]} > @@ -116,49 +119,45 @@ const AddCluster: React.FC = ({ rules={[ { required: true, - message: intl.formatMessage( - { id: 'common.form.rule.input' }, - { - name: 'Replicas' - } - ) + message: getRuleMessage('input', 'clusters.workerpool.replicas') } ]} > - + name="batch_size" rules={[ { required: true, - message: intl.formatMessage( - { id: 'common.form.rule.input' }, - { - name: 'Batch Size' - } - ) + message: getRuleMessage('input', 'clusters.workerpool.batchSize') } ]} > - + name="os_image" rules={[ { required: true, - message: intl.formatMessage( - { id: 'common.form.rule.input' }, - { - name: 'OS Image' - } - ) + message: getRuleMessage('input', 'clusters.workerpool.osImage') } ]} > - + + name="labels" rules={[ @@ -184,9 +183,9 @@ const AddCluster: React.FC = ({ ]} > diff --git a/src/pages/cluster-management/components/cloud-provider-form.tsx b/src/pages/cluster-management/components/cloud-provider-form.tsx index fa683168..561a4092 100644 --- a/src/pages/cluster-management/components/cloud-provider-form.tsx +++ b/src/pages/cluster-management/components/cloud-provider-form.tsx @@ -1,4 +1,5 @@ import SealSelect from '@/components/seal-form/seal-select'; +import useAppUtils from '@/hooks/use-app-utils'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; @@ -11,6 +12,7 @@ type OptionData = { label: string; datacenter: string; value: string; + icon: string; }; const OptionItem = styled.div` @@ -30,6 +32,9 @@ const OptionItem = styled.div` .value { color: var(--ant-color-text-secondary); } + .icon { + margin-right: 8px; + } `; interface CloudProviderProps { @@ -50,6 +55,7 @@ const optionRender = ( return ( + {data.icon} {data.label} {data.datacenter}{' '} {' '} @@ -65,6 +71,7 @@ const labelRender = (props: { const data = regionList.find((item) => item.value === props.value); return ( + {data?.icon} {data?.label} {data?.datacenter}{' '} @@ -76,6 +83,7 @@ const labelRender = (props: { const CloudProvider: React.FC = (props) => { const { credentialList } = props; const intl = useIntl(); + const { getRuleMessage } = useAppUtils(); return ( <> @@ -94,7 +102,7 @@ const CloudProvider: React.FC = (props) => { ]} > @@ -104,17 +112,12 @@ const CloudProvider: React.FC = (props) => { rules={[ { required: true, - message: intl.formatMessage( - { id: 'common.form.rule.input' }, - { - name: 'Region' - } - ) + message: getRuleMessage('input', 'clusters.workerpool.region') } ]} > { const CardHeight = 336; -const ClusterDetail: React.FC = ({ data }) => { +const ClusterMetrics = () => { const chartHeight = 160; - const [show, setShow] = React.useState(false); - const [addPoolStatus, setAddPoolStatus] = React.useState({ - open: false, - action: PageAction.CREATE, - title: '', - provider: ProviderValueMap.DigitalOcean, - currentData: null as NodePoolListItem | null, - clusterId: 0 - }); + const [searchParams] = useSearchParams(); + const id = searchParams.get('id'); + const provider = searchParams.get('provider'); const [detailContent, setDetailContent] = useState<{ current: { cpu: number; @@ -111,12 +103,12 @@ const ClusterDetail: React.FC = ({ data }) => { }); const getClusterDetail = async () => { - if (!data) { + if (!id) { return; } try { const response = await queryClusterDetail({ - cluster_id: data!.id + cluster_id: id }); setDetailContent({ current: response.system_load?.current, @@ -137,10 +129,10 @@ const ClusterDetail: React.FC = ({ data }) => { }; useEffect(() => { - if (data?.id) { + if (id) { getClusterDetail(); } - }, [data?.id]); + }, [id]); return (
@@ -225,13 +217,8 @@ const ClusterDetail: React.FC = ({ data }) => { - {data?.provider === ProviderValueMap.DigitalOcean && ( - <> - - - )}
); }; -export default ClusterDetail; +export default ClusterMetrics; diff --git a/src/pages/cluster-management/components/trend-chart.tsx b/src/pages/cluster-management/components/trend-chart.tsx index c7ee4232..c62428c3 100644 --- a/src/pages/cluster-management/components/trend-chart.tsx +++ b/src/pages/cluster-management/components/trend-chart.tsx @@ -63,6 +63,7 @@ const TrendChart: React.FC = ({ - }, - - { - key: 'delete', - label: 'common.button.delete', - icon: , - props: { - danger: true - } - } -]; - -interface WorkerPoolsProps { - loading?: boolean; - height?: string | number; - clusterData: ClusterListItem | null; -} - -const WorkerPools: React.FC = ({ - loading = false, - clusterData, - height = 'auto' -}) => { +const WorkerPools = () => { + const [searchParams] = useSearchParams(); const { dataSource, rowSelection, queryParams, modalRef, + sortOrder, fetchData, handleDelete, handleDeleteBatch, @@ -77,9 +50,9 @@ const WorkerPools: React.FC = ({ deleteAPI: deleteWorkerPool, API: WORKER_POOLS_API, watch: false, - contentForDelete: 'resources.modelfiles.modelfile', + contentForDelete: 'clusters.workerpool.title', defaultQueryParams: { - cluster_id: clusterData!.id + cluster_id: searchParams.get('id') || undefined } }); const intl = useIntl(); @@ -89,7 +62,7 @@ const WorkerPools: React.FC = ({ title: '', provider: ProviderValueMap.DigitalOcean, currentData: null as ListItem | null, - clusterId: 0 + clusterId: 0 as number | string }); // pool action handler @@ -98,29 +71,43 @@ const WorkerPools: React.FC = ({ setAddPoolStatus({ open: true, action: PageAction.EDIT, - title: 'Edit Worker Pool', - provider: clusterData!.provider, + title: intl.formatMessage( + { id: 'common.button.edit.item' }, + { name: record.instance_type } + ), + provider: searchParams.get('provider') || '', currentData: record, - clusterId: clusterData!.id + clusterId: searchParams.get('id') || 0 }); } }; - const onSelect = (key: string, record: ListItem) => { + const handleAddPool = (row: ListItem) => { + setAddPoolStatus({ + open: true, + action: PageAction.CREATE, + title: intl.formatMessage({ id: 'clusters.button.addNodePool' }), + provider: searchParams.get('provider') || '', + clusterId: searchParams.get('id') || 0, + currentData: null + }); + }; + + const onSelect = useMemoizedFn((key: string, record: ListItem) => { if (key === 'delete') { handleDelete({ ...record, name: record.instance_type }); } if (key === 'edit') { handleEdit(key, record); } - }; + }); const handleSubmitWorkerPool = async (formdata: NodePoolFormData) => { try { if (addPoolStatus.action === PageAction.CREATE) { await createWorkerPool({ data: formdata, - clusterId: clusterData!.id + clusterId: searchParams.get('id') || 0 }); } if (addPoolStatus.action === PageAction.EDIT) { @@ -140,81 +127,64 @@ const WorkerPools: React.FC = ({ }); }; - const columns = [ - { - title: 'Instance Type', - dataIndex: 'instance_type', - key: 'instance_type' - }, - { - title: 'Replicas', - dataIndex: 'replicas', - key: 'replicas' - }, - { - title: 'Batch Size', - dataIndex: 'batch_size', - key: 'batch_size' - }, - { - title: 'Labels', - dataIndex: 'labels', - key: 'labels', - render: (text: string, record: ListItem) => ( - - ) - }, - { - title: 'Create Time', - dataIndex: 'create_at', - key: 'created_at', - render: (text: string) => dayjs(text).format('YYYY-MM-DD HH:mm:ss') - }, - { - title: 'Operations', - key: 'operations', - render: (text: string, record: ListItem) => ( - onSelect?.(key, record)} - /> - ) - } - ]; + const columns = usePoolsColumns(sortOrder, onSelect); return ( <> - Worker Pools + {intl.formatMessage({ id: 'clusters.workerpool.title' })} -
- - { - setAddPoolStatus({ - open: false, - action: PageAction.CREATE, - title: '', - provider: ProviderValueMap.DigitalOcean, - currentData: null, - clusterId: 0 - }); - }} - onOk={handleSubmitWorkerPool} - > - - + +
+ { + setAddPoolStatus({ + open: false, + action: PageAction.CREATE, + title: '', + provider: ProviderValueMap.DigitalOcean, + currentData: null, + clusterId: 0 + }); + }} + onOk={handleSubmitWorkerPool} + > + ); }; diff --git a/src/pages/cluster-management/config/index.ts b/src/pages/cluster-management/config/index.ts index c4384809..4e124de5 100644 --- a/src/pages/cluster-management/config/index.ts +++ b/src/pages/cluster-management/config/index.ts @@ -87,11 +87,6 @@ export const clusterActionList = [ label: 'common.button.edit', icon: icons.EditOutlined }, - { - key: 'details', - label: 'common.button.detail', - icon: icons.FileTextOutlined - }, { key: 'add_worker', label: 'resources.button.create', @@ -127,20 +122,36 @@ export const regionList: { label: string; datacenter: string; value: string; + icon: string; }[] = [ - { label: 'New York', datacenter: 'Datacenter 1', value: 'nyc1' }, - { label: 'New York', datacenter: 'Datacenter 2', value: 'nyc2' }, - { label: 'New York', datacenter: 'Datacenter 3', value: 'nyc3' }, - { label: 'Toronto', datacenter: 'Datacenter 1', value: 'tor1' }, - { label: 'San Francisco', datacenter: 'Datacenter 1', value: 'sfo1' }, - { label: 'San Francisco', datacenter: 'Datacenter 2', value: 'sfo2' }, - { label: 'San Francisco', datacenter: 'Datacenter 3', value: 'sfo3' }, - { label: 'Atlanta', datacenter: 'Datacenter 1', value: 'atl1' }, - { label: 'Singapore', datacenter: 'Datacenter 1', value: 'sgp1' }, - { label: 'Bangalore', datacenter: 'Datacenter 1', value: 'blr1' }, - { label: 'London', datacenter: 'Datacenter 1', value: 'lon1' }, - { label: 'Amsterdam', datacenter: 'Datacenter 2', value: 'ams2' }, - { label: 'Amsterdam', datacenter: 'Datacenter 3', value: 'ams3' }, - { label: 'Frankfurt', datacenter: 'Datacenter 1', value: 'fra1' }, - { label: 'Sydney', datacenter: 'Datacenter 1', value: 'syd1' } + { label: 'New York', datacenter: 'Datacenter 1', value: 'nyc1', icon: '🇺🇸' }, + { label: 'New York', datacenter: 'Datacenter 2', value: 'nyc2', icon: '🇺🇸' }, + { label: 'New York', datacenter: 'Datacenter 3', value: 'nyc3', icon: '🇺🇸' }, + { label: 'Toronto', datacenter: 'Datacenter 1', value: 'tor1', icon: '🇨🇦' }, + { + label: 'San Francisco', + datacenter: 'Datacenter 1', + value: 'sfo1', + icon: '🇺🇸' + }, + { + label: 'San Francisco', + datacenter: 'Datacenter 2', + value: 'sfo2', + icon: '🇺🇸' + }, + { + label: 'San Francisco', + datacenter: 'Datacenter 3', + value: 'sfo3', + icon: '🇺🇸' + }, + { label: 'Atlanta', datacenter: 'Datacenter 1', value: 'atl1', icon: '🇺🇸' }, + { label: 'Singapore', datacenter: 'Datacenter 1', value: 'sgp1', icon: '🇸🇬' }, + { label: 'Bangalore', datacenter: 'Datacenter 1', value: 'blr1', icon: '🇮🇳' }, + { label: 'London', datacenter: 'Datacenter 1', value: 'lon1', icon: '🇬🇧' }, + { label: 'Amsterdam', datacenter: 'Datacenter 2', value: 'ams2', icon: '🇳🇱' }, + { label: 'Amsterdam', datacenter: 'Datacenter 3', value: 'ams3', icon: '🇳🇱' }, + { label: 'Frankfurt', datacenter: 'Datacenter 1', value: 'fra1', icon: '🇩🇪' }, + { label: 'Sydney', datacenter: 'Datacenter 1', value: 'syd1', icon: '🇦🇺' } ]; diff --git a/src/pages/cluster-management/hooks/use-cluster-columns.tsx b/src/pages/cluster-management/hooks/use-cluster-columns.tsx index e9dd76f8..a66880b7 100644 --- a/src/pages/cluster-management/hooks/use-cluster-columns.tsx +++ b/src/pages/cluster-management/hooks/use-cluster-columns.tsx @@ -2,7 +2,7 @@ import AutoTooltip from '@/components/auto-tooltip'; import DropdownButtons from '@/components/drop-down-buttons'; import StatusTag from '@/components/status-tag'; -import { useIntl } from '@umijs/max'; +import { Link, useIntl } from '@umijs/max'; import { ColumnsType } from 'antd/es/table'; import dayjs from 'dayjs'; import { useMemo } from 'react'; @@ -14,25 +14,34 @@ import { } from '../config'; import { ClusterListItem } from '../config/types'; +const setActionsItems = (row: ClusterListItem) => { + return clusterActionList.filter((item) => { + if (item.provider) { + return item.provider === row.provider; + } + return true; + }); +}; + const useClusterColumns = ( handleSelect: (val: string, record: ClusterListItem) => void ): ColumnsType => { const intl = useIntl(); - const setActionsItems = (row: ClusterListItem) => { - return clusterActionList.filter((item) => { - if (item.provider) { - return item.provider === row.provider; - } - return true; - }); - }; return useMemo(() => { return [ { title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: 'name', - render: (text: string) => {text} + render: (text: string, record: ClusterListItem) => ( + + + {text} + + + ) }, { title: intl.formatMessage({ id: 'clusters.table.provider' }), diff --git a/src/pages/cluster-management/hooks/use-pools-columns.tsx b/src/pages/cluster-management/hooks/use-pools-columns.tsx new file mode 100644 index 00000000..212636ed --- /dev/null +++ b/src/pages/cluster-management/hooks/use-pools-columns.tsx @@ -0,0 +1,108 @@ +import AutoTooltip from '@/components/auto-tooltip'; +import DropdownButtons from '@/components/drop-down-buttons'; +import LabelsCell from '@/components/label-cell'; +import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { ColumnsType } from 'antd/es/table'; +import type { SortOrder } from 'antd/es/table/interface'; +import dayjs from 'dayjs'; +import { useMemo } from 'react'; +import { NodePoolListItem as ListItem } from '../config/types'; + +const actionItems = [ + { + key: 'edit', + label: 'common.button.edit', + icon: + }, + + { + key: 'delete', + label: 'common.button.delete', + icon: , + props: { + danger: true + } + } +]; +const usePoolsColumns = ( + sortOrder: SortOrder, + handleSelect: (val: string, record: ListItem) => void +): ColumnsType => { + const intl = useIntl(); + + return useMemo(() => { + return [ + { + title: intl.formatMessage({ id: 'clusters.workerpool.instanceType' }), + dataIndex: 'instance_type', + key: 'instance_type', + ellipsis: { + showTitle: false + }, + render: (text: string) => ( + + {text} + + ) + }, + { + title: intl.formatMessage({ id: 'clusters.workerpool.replicas' }), + dataIndex: 'replicas', + key: 'replicas' + }, + { + title: intl.formatMessage({ id: 'clusters.workerpool.batchSize' }), + dataIndex: 'batch_size', + key: 'batch_size' + }, + { + title: intl.formatMessage({ id: 'clusters.workerpool.osImage' }), + dataIndex: 'os_image', + key: 'os_image', + ellipsis: { + showTitle: false + }, + render: (text: string) => ( + + {text} + + ) + }, + { + title: intl.formatMessage({ id: 'resources.table.labels' }), + dataIndex: 'labels', + key: 'labels', + width: 200, + render: (text: string, record: ListItem) => ( + + ) + }, + { + title: intl.formatMessage({ id: 'common.table.createTime' }), + dataIndex: 'create_at', + key: 'created_at', + showSorterTooltip: false, + defaultSortOrder: 'descend', + sortOrder: sortOrder, + sorter: false, + ellipsis: { + showTitle: false + }, + render: (text: string) => dayjs(text).format('YYYY-MM-DD HH:mm:ss') + }, + { + title: intl.formatMessage({ id: 'common.table.operation' }), + key: 'operations', + render: (text: string, record: ListItem) => ( + handleSelect?.(key, record)} + /> + ) + } + ]; + }, [sortOrder, handleSelect]); +}; + +export default usePoolsColumns; diff --git a/src/pages/cluster-management/layout/index.tsx b/src/pages/cluster-management/layout/index.tsx new file mode 100644 index 00000000..43f78054 --- /dev/null +++ b/src/pages/cluster-management/layout/index.tsx @@ -0,0 +1,20 @@ +import { Outlet } from '@umijs/max'; +import React, { useEffect, useRef, useState } from 'react'; + +const ListLayout: React.FC = () => { + const [listParams, setListParams] = useState({ page: 1, keyword: '' }); + const scrollRef = useRef(0); + + useEffect(() => { + console.log('cluster layout:'); + }, []); + + return ( +
+ {/* 提供上下文给子路由:ListPage & DetailPage */} + +
+ ); +}; + +export default ListLayout; diff --git a/src/pages/llmodels/components/data-form.tsx b/src/pages/llmodels/components/data-form.tsx index b5c4a48f..02d9c25d 100644 --- a/src/pages/llmodels/components/data-form.tsx +++ b/src/pages/llmodels/components/data-form.tsx @@ -146,34 +146,30 @@ const DataForm: React.FC = forwardRef((props, ref) => { onValuesChange?.(changedValues, allValues); }; - useImperativeHandle( - ref, - () => { - return { - form: form, - submit: handleSumit, - resetFields: (fields: any[]) => { - form.resetFields(fields); - }, - setFieldsValue: (values: FormData) => { - form.setFieldsValue(values); - }, - setFieldValue: (name: string, value: any) => { - form.setFieldValue(name, value); - }, - getFieldValue: (name: string) => { - return form.getFieldValue(name); - }, - getFieldsValue: () => { - return form.getFieldsValue(); - }, - getGPUOptionList(params: { clusterId: number }) { - getGPUOptionList(params); - } - }; - }, - [] - ); + useImperativeHandle(ref, () => { + return { + form: form, + submit: handleSumit, + resetFields: (fields: any[]) => { + form.resetFields(fields); + }, + setFieldsValue: (values: FormData) => { + form.setFieldsValue(values); + }, + setFieldValue: (name: string, value: any) => { + form.setFieldValue(name, value); + }, + getFieldValue: (name: string) => { + return form.getFieldValue(name); + }, + getFieldsValue: () => { + return form.getFieldsValue(); + }, + getGPUOptionList(params: { clusterId: number }) { + getGPUOptionList(params); + } + }; + }, []); return ( = forwardRef((props, ref) => { rules={[ { required: true, - message: getRuleMessage('select', 'Cluster', false) + message: getRuleMessage('select', 'clusters.title') } ]} > { diff --git a/src/pages/resources/apis/index.ts b/src/pages/resources/apis/index.ts index 38089a1b..616381b1 100644 --- a/src/pages/resources/apis/index.ts +++ b/src/pages/resources/apis/index.ts @@ -5,6 +5,12 @@ export const WORKERS_API = '/workers'; export const GPU_DEVICES_API = '/gpu-devices'; export const MODEL_FILES_API = '/model-files'; +export async function downloadWorkerPrivateKey(id: string | number) { + return request(`${WORKERS_API}/${id}/privatekey`, { + method: 'GET' + }); +} + export async function queryWorkersList>( params: Global.SearchParams & T ) { diff --git a/src/pages/resources/components/container-install.tsx b/src/pages/resources/components/container-install.tsx index 027c7384..12d93d75 100644 --- a/src/pages/resources/components/container-install.tsx +++ b/src/pages/resources/components/container-install.tsx @@ -47,6 +47,7 @@ const AddWorker: React.FC = (props) => { return commandCode?.registerWorker({ server: registrationInfo.server_url || origin, tag: tag, + image: registrationInfo.image, token: registrationInfo.token || '${token}', workerip: '${workerip}' }); diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index 0f92fa2d..7d54d8ab 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -10,6 +10,7 @@ import React, { useEffect, useState } from 'react'; import { WORKERS_API, deleteWorker, + downloadWorkerPrivateKey, queryWorkersList, updateWorker } from '../apis'; @@ -144,6 +145,9 @@ const Workers: React.FC = () => { if (val === 'details') { handleViewDetail(record); } + if (val === 'download_ssh_key') { + downloadWorkerPrivateKey(record.id); + } }); const renderEmpty = (type?: string) => { diff --git a/src/pages/resources/config/index.ts b/src/pages/resources/config/index.ts index 11b24bb7..4e2c9636 100644 --- a/src/pages/resources/config/index.ts +++ b/src/pages/resources/config/index.ts @@ -5,7 +5,8 @@ export const WorkerStatusMap = { not_ready: 'not_ready', unreachable: 'unreachable', provisioning: 'provisioning', - deleting: 'deleting' + deleting: 'deleting', + error: 'error' }; export const WorkerStatusMapValue = { @@ -13,7 +14,8 @@ export const WorkerStatusMapValue = { [WorkerStatusMap.not_ready]: 'Not Ready', [WorkerStatusMap.unreachable]: 'Unreachable', [WorkerStatusMap.provisioning]: 'Provisioning', - [WorkerStatusMap.deleting]: 'Deleting' + [WorkerStatusMap.deleting]: 'Deleting', + [WorkerStatusMap.error]: 'Error' }; export const status: any = { @@ -21,21 +23,22 @@ export const status: any = { [WorkerStatusMap.not_ready]: StatusMaps.error, [WorkerStatusMap.unreachable]: StatusMaps.error, [WorkerStatusMap.provisioning]: StatusMaps.transitioning, - [WorkerStatusMap.deleting]: StatusMaps.warning + [WorkerStatusMap.deleting]: StatusMaps.warning, + [WorkerStatusMap.error]: StatusMaps.error }; export const addWorkerGuide: Record = { mac: { getToken: 'cat /var/lib/gpustack/token', registerWorker(params: { server: string; token: string }) { - return `curl -sfL https://get.gpustack.ai | sh -s - --server-url ${params.server} --registration ${params.token}`; + return `curl -sfL https://get.gpustack.ai | sh -s - --server-url ${params.server} --registration-token ${params.token}`; } }, win: { getToken: 'Get-Content -Path (Join-Path -Path $env:APPDATA -ChildPath "gpustack\\token") -Raw', registerWorker(params: { server: string; token: string }) { - return `Invoke-Expression "& { $((Invoke-WebRequest -Uri 'https://get.gpustack.ai' -UseBasicParsing).Content) } --server-url '${params.server}' --registration '${params.token}'"`; + return `Invoke-Expression "& { $((Invoke-WebRequest -Uri 'https://get.gpustack.ai' -UseBasicParsing).Content) } --server-url '${params.server}' --registration-token '${params.token}'"`; } }, cuda: { @@ -45,6 +48,7 @@ export const addWorkerGuide: Record = { server: string; tag: string; token: string; + image: string; workerip: string; }) { return `docker run -d --name gpustack \\ @@ -53,8 +57,8 @@ export const addWorkerGuide: Record = { --network=host \\ --ipc=host \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, npu: { @@ -65,6 +69,7 @@ export const addWorkerGuide: Record = { tag: string; token: string; workerip: string; + image: string; }) { return `docker run -d --name gpustack \\ --restart=unless-stopped \\ @@ -81,8 +86,8 @@ export const addWorkerGuide: Record = { --network=host \\ --ipc=host \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, npu310p: { @@ -93,6 +98,7 @@ export const addWorkerGuide: Record = { tag: string; token: string; workerip: string; + image: string; }) { return `docker run -d --name gpustack \\ --restart=unless-stopped \\ @@ -109,8 +115,8 @@ export const addWorkerGuide: Record = { --network=host \\ --ipc=host \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag}-310p \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} -310p \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, musa: { @@ -121,14 +127,15 @@ export const addWorkerGuide: Record = { tag: string; token: string; workerip: string; + image: string; }) { return `docker run -d --name gpustack \\ --restart=unless-stopped \\ --network=host \\ --ipc=host \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, cpu: { @@ -139,13 +146,14 @@ export const addWorkerGuide: Record = { tag: string; token: string; workerip: string; + image: string; }) { return `docker run -d --name gpustack \\ --restart=unless-stopped \\ --network=host \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, rocm: { @@ -154,6 +162,7 @@ export const addWorkerGuide: Record = { tag: string; token: string; workerip: string; + image: string; }) { return `docker run -d --name gpustack \\ --restart=unless-stopped \\ @@ -165,8 +174,8 @@ export const addWorkerGuide: Record = { --cap-add=SYS_PTRACE \\ --security-opt seccomp=unconfined \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, dcu: { @@ -175,6 +184,7 @@ export const addWorkerGuide: Record = { tag: string; token: string; workerip: string; + image: string; }) { return `docker run -d --name gpustack \\ --restart=unless-stopped \\ @@ -188,8 +198,8 @@ export const addWorkerGuide: Record = { --cap-add=SYS_PTRACE \\ --security-opt seccomp=unconfined \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, corex: { @@ -198,6 +208,7 @@ export const addWorkerGuide: Record = { tag: string; token: string; workerip: string; + image: string; }) { return `docker run -d --name gpustack \\ -v /lib/modules:/lib/modules \\ @@ -209,8 +220,8 @@ export const addWorkerGuide: Record = { --network=host \\ --ipc=host \\ -v gpustack-data:/var/lib/gpustack \\ - gpustack/gpustack:${params.tag} \\ - --server-url ${params.server} --registration ${params.token} --worker-ip ${params.workerip}`; + ${params.image} \\ + --server-url ${params.server} --registration-token ${params.token} --worker-ip ${params.workerip}`; } }, container: { diff --git a/src/pages/resources/config/types.ts b/src/pages/resources/config/types.ts index eb8a8ef8..62c4f725 100644 --- a/src/pages/resources/config/types.ts +++ b/src/pages/resources/config/types.ts @@ -66,6 +66,8 @@ export interface ListItem { ip: string; cluster_id: number; state_message: string; + ssh_key_id: string; + progress: string; status: { cpu: { total: number; diff --git a/src/pages/resources/hooks/use-worker-columns.tsx b/src/pages/resources/hooks/use-worker-columns.tsx index 6aa98d71..4c6d976a 100644 --- a/src/pages/resources/hooks/use-worker-columns.tsx +++ b/src/pages/resources/hooks/use-worker-columns.tsx @@ -1,14 +1,13 @@ import AutoTooltip from '@/components/auto-tooltip'; import DropdownButtons from '@/components/drop-down-buttons'; -import IconFont from '@/components/icon-font'; import LabelsCell from '@/components/label-cell'; import ProgressBar from '@/components/progress-bar'; import InfoColumn from '@/components/simple-table/info-column'; import StatusTag from '@/components/status-tag'; import { convertFileSize } from '@/utils'; import { - CodeOutlined, DeleteOutlined, + DownloadOutlined, EditOutlined, FileTextOutlined, InfoCircleOutlined @@ -18,7 +17,7 @@ import { Tooltip } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import _ from 'lodash'; import { useMemo } from 'react'; -import { WorkerStatusMap, WorkerStatusMapValue, status } from '../config'; +import { WorkerStatusMapValue, status } from '../config'; import { Filesystem, GPUDeviceItem, ListItem } from '../config/types'; const ActionList = [ @@ -29,12 +28,17 @@ const ActionList = [ icon: }, { - label: 'common.button.logs', - locale: false, - key: 'logs', - icon: + label: 'resources.worker.download.privatekey', + key: 'download_ssh_key', + icon: }, - { label: 'Terminal', locale: false, key: 'terminal', icon: }, + // { + // label: 'common.button.logs', + // locale: false, + // key: 'logs', + // icon: + // }, + // { label: 'Terminal', locale: false, key: 'terminal', icon: }, { label: 'common.button.delete', key: 'delete', @@ -45,8 +49,8 @@ const ActionList = [ const setActions = (row: ListItem) => { return ActionList.filter((action) => { - if (action.key === 'details' && row.state === WorkerStatusMap.ready) { - return true; + if (action.key === 'download_ssh_key') { + return !!row.ssh_key_id; } return true; }); @@ -224,6 +228,7 @@ const useWorkerColumns = ({ render: (_, record) => (