From bd6f4b3f762207a90423e8ed59a9630e37f606ef Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 17 Dec 2025 14:40:59 +0800 Subject: [PATCH] fix: add worker, create cluster --- src/hooks/use-update-chunk-list.ts | 1 + src/pages/cluster-management/apis/index.ts | 11 ++- .../cluster-management/cluster-create.tsx | 2 +- .../components/add-worker/add-worker-step.tsx | 23 ++++- .../components/add-worker/index.tsx | 51 +++++++++- .../components/add-worker/summary-data.tsx | 2 +- .../components/cloud-provider-form.tsx | 23 +++++ .../components/cluster-form.tsx | 9 +- src/pages/cluster-management/config/types.ts | 6 ++ .../config/worker-config.json | 6 +- .../config/yaml-template.ts | 83 ++++++++-------- .../hooks/use-add-worker-message.ts | 1 + .../services/use-system-config.ts | 25 +++++ .../step-forms/advance-config.tsx | 94 ++++++++++--------- 14 files changed, 234 insertions(+), 103 deletions(-) create mode 100644 src/pages/cluster-management/services/use-system-config.ts diff --git a/src/hooks/use-update-chunk-list.ts b/src/hooks/use-update-chunk-list.ts index e30127f8..e71268ef 100644 --- a/src/hooks/use-update-chunk-list.ts +++ b/src/hooks/use-update-chunk-list.ts @@ -45,6 +45,7 @@ export function useUpdateChunkedList(options: { }, 200); }; const updateChunkedList = (data: ChunkedCollection) => { + console.log('updateChunkedList data:', data); let collections = data?.collection || []; if (options?.computedID) { collections = collections?.map((item: any) => { diff --git a/src/pages/cluster-management/apis/index.ts b/src/pages/cluster-management/apis/index.ts index bf446425..b6f19a82 100644 --- a/src/pages/cluster-management/apis/index.ts +++ b/src/pages/cluster-management/apis/index.ts @@ -6,7 +6,8 @@ import { CredentialFormData, CredentialListItem, NodePoolFormData, - NodePoolListItem + NodePoolListItem, + SystemConfig } from '../config/types'; export const CREDENTIALS_API = '/cloud-credentials'; @@ -19,6 +20,8 @@ export const CLUSTER_TOKEN = 'registration-token'; export const PROVIDER_PROXY_API = '/provider-proxy'; +export const SYSTEM_CONFIG_API = '/config'; + // ============= DigitalOcean start ===================== export const REGIONS_API = '/v2/regions'; @@ -189,3 +192,9 @@ export async function deleteWorkerPool(id: number) { method: 'DELETE' }); } + +export async function querySystemConfig() { + return request(`${SYSTEM_CONFIG_API}`, { + method: 'GET' + }); +} diff --git a/src/pages/cluster-management/cluster-create.tsx b/src/pages/cluster-management/cluster-create.tsx index 42e10c76..a816c26d 100644 --- a/src/pages/cluster-management/cluster-create.tsx +++ b/src/pages/cluster-management/cluster-create.tsx @@ -272,7 +272,7 @@ const ClusterCreate = () => { const breadcrumbItems = [ { title: {intl.formatMessage({ id: 'clusters.title' })}, - onClick: () => navigate(-1) + onClick: () => handleCancel() }, { title: intl.formatMessage({ id: 'common.button.create' }) diff --git a/src/pages/cluster-management/components/add-worker/add-worker-step.tsx b/src/pages/cluster-management/components/add-worker/add-worker-step.tsx index 4fd23891..2794a3c2 100644 --- a/src/pages/cluster-management/components/add-worker/add-worker-step.tsx +++ b/src/pages/cluster-management/components/add-worker/add-worker-step.tsx @@ -2,6 +2,7 @@ import AlertInfoBlock from '@/components/alert-info/block'; import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message'; import { ExclamationCircleFilled } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; +import { Alert } from 'antd'; import React from 'react'; import styled from 'styled-components'; import { ProviderType, ProviderValueMap } from '../../config'; @@ -29,6 +30,7 @@ const Container = styled.div` * clusterList and onClusterChange are only required when from worker page. */ type AddWorkerProps = { + isModal?: boolean; provider: ProviderType; clusterList?: Global.BaseOption[]; clusterLoading?: boolean; @@ -49,6 +51,7 @@ type AddWorkerProps = { */ const AddWorkerSteps: React.FC = (props) => { const { + isModal = false, registrationInfo, provider, clusterList, @@ -63,7 +66,7 @@ const AddWorkerSteps: React.FC = (props) => { ); const startWatchRef = React.useRef(false); const { update, summary, register } = useSummaryStatus(); - const { contextHolder, createModelsChunkRequest } = useAddWorkerMessage({ + const { addedCount, createModelsChunkRequest } = useAddWorkerMessage({ startWatch: startWatchRef }); @@ -84,8 +87,10 @@ const AddWorkerSteps: React.FC = (props) => { }, [stepList]); React.useEffect(() => { - createModelsChunkRequest(); - }, []); + if (!isModal) { + createModelsChunkRequest(); + } + }, [isModal]); return ( = (props) => { )} )} + {addedCount > 0 && ( + + )} - {contextHolder} ); }; diff --git a/src/pages/cluster-management/components/add-worker/index.tsx b/src/pages/cluster-management/components/add-worker/index.tsx index 87188eec..29cac6db 100644 --- a/src/pages/cluster-management/components/add-worker/index.tsx +++ b/src/pages/cluster-management/components/add-worker/index.tsx @@ -1,11 +1,22 @@ import ScrollerModal from '@/components/scroller-modal'; +import useAddWorkerMessage from '@/pages/cluster-management/hooks/use-add-worker-message'; +import { useIntl } from '@umijs/max'; +import { Alert, Button } from 'antd'; import React, { useEffect } from 'react'; +import styled from 'styled-components'; import { queryClusterToken } from '../../apis'; import { ProviderType } from '../../config'; import { ClusterListItem } from '../../config/types'; import AddWorkerStep from './add-worker-step'; import { StepName } from './config'; +const Footer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 12px; +`; + type AddWorkerProps = { open: boolean; provider: ProviderType; @@ -40,6 +51,11 @@ const AddWorker: React.FC = (props) => { clusterLoading, stepList = [] } = props || {}; + const intl = useIntl(); + const startWatchRef = React.useRef(false); + const { addedCount, createModelsChunkRequest } = useAddWorkerMessage({ + startWatch: startWatchRef + }); const firstLoad = React.useRef(true); const [registrationInfo, setRegistrationInfo] = React.useState<{ token: string; @@ -61,6 +77,7 @@ const AddWorker: React.FC = (props) => { ...data, cluster_id: value }); + startWatchRef.current = true; } catch (error) { firstLoad.current = false; } @@ -75,6 +92,12 @@ const AddWorker: React.FC = (props) => { }; }, [open, cluster_id]); + useEffect(() => { + if (open) { + createModelsChunkRequest(); + } + }, [open]); + return ( = (props) => { width={865} style={{}} maxContentHeight={'max(calc(100vh - 200px), 600px)'} - footer={null} + footer={ +
+ + {addedCount > 0 && ( + + )} + + +
+ } > { tips={ workerIPConfig.enable ? workerIPConfig.ip - ? workerIPConfig.ip + ? '' : intl.formatMessage({ id: 'clusters.addworker.notSpecified' }) : intl.formatMessage({ id: 'clusters.addworker.autoDetect' }) } diff --git a/src/pages/cluster-management/components/cloud-provider-form.tsx b/src/pages/cluster-management/components/cloud-provider-form.tsx index f328f335..7fb60b1f 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 { fromClusterCreationAtom } from '@/atoms/clusters'; +import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; @@ -11,6 +12,7 @@ import React, { useEffect } from 'react'; import styled from 'styled-components'; import { ClusterFormData as FormData } from '../config/types'; import { useProviderRegions } from '../hooks/use-provider-regions'; +import useSystemConfig from '../services/use-system-config'; const OptionItem = styled.div` display: flex; @@ -74,6 +76,8 @@ const optionRender = (option: any): React.ReactNode => { const CloudProvider: React.FC = (props) => { const { credentialList, action, credentialID } = props; const intl = useIntl(); + const { systemConfig } = useSystemConfig(); + const form = Form.useFormInstance(); const { getRegions, @@ -105,6 +109,10 @@ const CloudProvider: React.FC = (props) => { } }, [credentialID]); + useEffect(() => { + form.setFieldValue('server_url', systemConfig?.server_external_url || ''); + }, [systemConfig]); + const labelRender = (props: { label: React.ReactNode; value: string | number; @@ -176,6 +184,21 @@ const CloudProvider: React.FC = (props) => { })} > + + name="server_url" + rules={[ + { + required: true, + message: getRuleMessage('input', 'clusters.create.serverUrl') + } + ]} + > + + ); }; diff --git a/src/pages/cluster-management/components/cluster-form.tsx b/src/pages/cluster-management/components/cluster-form.tsx index 22567586..1ece3701 100644 --- a/src/pages/cluster-management/components/cluster-form.tsx +++ b/src/pages/cluster-management/components/cluster-form.tsx @@ -40,9 +40,7 @@ const ClusterForm: React.FC = forwardRef( form.setFieldsValue(currentData); if (advanceConfigRef.current) { - const workerConfigYaml = json2Yaml({ - worker_config: currentData.worker_config || {} - }); + const workerConfigYaml = json2Yaml(currentData.worker_config || {}); advanceConfigRef.current?.setYamlValue(workerConfigYaml); } } @@ -69,7 +67,9 @@ const ClusterForm: React.FC = forwardRef( return { ...values, - ...workerConfig + worker_config: { + ...workerConfig + } }; } })); @@ -129,6 +129,7 @@ const ClusterForm: React.FC = forwardRef( children: ( ) diff --git a/src/pages/cluster-management/config/types.ts b/src/pages/cluster-management/config/types.ts index 7ca3c971..dcd22366 100644 --- a/src/pages/cluster-management/config/types.ts +++ b/src/pages/cluster-management/config/types.ts @@ -79,3 +79,9 @@ export interface ClusterFormData { worker_config?: Record; worker_pools?: NodePoolFormData[]; } + +export interface SystemConfig { + debug: boolean; + server_external_url: string; + system_default_container_registry: string; +} diff --git a/src/pages/cluster-management/config/worker-config.json b/src/pages/cluster-management/config/worker-config.json index 8f81f734..6b19e254 100644 --- a/src/pages/cluster-management/config/worker-config.json +++ b/src/pages/cluster-management/config/worker-config.json @@ -36,7 +36,7 @@ "gateway_mode": { "type": "string", "description": "Gateway mode", - "enum": ["worker", "gateway", "disabled"] + "enum": ["worker", "gateway", "disabled", "auto"] }, "gateway_kubeconfig": { "type": "string", @@ -88,9 +88,7 @@ "resources": { "type": "object", "description": "Additional resource definitions", - "additionalProperties": { - "type": "object" - } + "additionalProperties": {} }, "pipx_path": { "type": "string", diff --git a/src/pages/cluster-management/config/yaml-template.ts b/src/pages/cluster-management/config/yaml-template.ts index 766c6f03..b22d9211 100644 --- a/src/pages/cluster-management/config/yaml-template.ts +++ b/src/pages/cluster-management/config/yaml-template.ts @@ -1,53 +1,54 @@ export default `# This is a template for worker_config. -worker_config: - debug: true +# debug: false - # directories - cache_dir: - log_dir: - bin_dir: +# ========= directories =========== - # container & image - system_default_container_registry: - image_repo: gpustack/worker - image_name_override: "" +# cache_dir: "/var/lib/gpustack/cache" +# log_dir: "/var/lib/gpustack/log" +# bin_dir: "/var/lib/gpustack/bin" - # gateway - gateway_mode: - gateway_concurrency: 0 - gateway_kubeconfig: "" +# ========= container & image =========== - # service & networking - service_discovery_name: - namespace: default - worker_port: - worker_metrics_port: - service_port_range: - ray_port_range: +# system_default_container_registry: "docker.io" +# image_name_override: "gpustack/gpustack:main" +# image_repo: "gpustack/gpustack" - # resources - resources: - gpu: - count: - type: - cpu: - limit: - memory: - limit: +# ========= gateway =========== - # huggingface - huggingface_token: "" - enable_hf_transfer: true - enable_hf_xet: true +# gateway_mode: "auto" +# gateway_concurrency: 16 +# gateway_kubeconfig: "/var/lib/gpustack/higress/kubeconfig" - # metrics - disable_worker_metrics: false +# ========= service & networking =========== - # tools & runtime - pipx_path: - tools_download_base_url: +# service_discovery_name: "worker" +# namespace: "gpustack-system" +# worker_port: 10150 +# worker_metrics_port: 10150 +# service_port_range: "40000-40063" +# ray_port_range: "41000-41999" - # proxy - proxy_mode: worker +# ========= resources =========== + +# resources: + +# ========= huggingface =========== + +# huggingface_token: +# enable_hf_transfer: false +# enable_hf_xet: false + +# ========= metrics =========== + +# disable_worker_metrics: false + +# ========= tools & runtime =========== + +# pipx_path: "/usr/local/bin/pipx" +# tools_download_base_url: + +# ========= proxy =========== + +# proxy_mode: worker `; diff --git a/src/pages/cluster-management/hooks/use-add-worker-message.ts b/src/pages/cluster-management/hooks/use-add-worker-message.ts index 524a37fb..d32ebc73 100644 --- a/src/pages/cluster-management/hooks/use-add-worker-message.ts +++ b/src/pages/cluster-management/hooks/use-add-worker-message.ts @@ -25,6 +25,7 @@ export default function useAddWorkerMessage(params: { onCreate: (newItems: any) => { if (params.startWatch?.current) { newItemsRef.current = newItemsRef.current.concat(newItems); + console.log('newItemsRef.current:', newItemsRef.current); showAddWorkerMessage(); } } diff --git a/src/pages/cluster-management/services/use-system-config.ts b/src/pages/cluster-management/services/use-system-config.ts new file mode 100644 index 00000000..5886626e --- /dev/null +++ b/src/pages/cluster-management/services/use-system-config.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; +import { querySystemConfig } from '../apis'; +import { SystemConfig } from '../config/types'; + +export default function useSystemConfig() { + const [systemConfig, setSystemConfig] = useState( + {} as SystemConfig + ); + + const fetchSystemConfig = async () => { + try { + const data = await querySystemConfig(); + setSystemConfig(data); + } catch (error) { + // handle error if needed + setSystemConfig({} as SystemConfig); + } + }; + + useEffect(() => { + fetchSystemConfig(); + }, []); + + return { systemConfig }; +} diff --git a/src/pages/cluster-management/step-forms/advance-config.tsx b/src/pages/cluster-management/step-forms/advance-config.tsx index b7144fbb..310546de 100644 --- a/src/pages/cluster-management/step-forms/advance-config.tsx +++ b/src/pages/cluster-management/step-forms/advance-config.tsx @@ -4,29 +4,34 @@ import YamlEditor from '@/pages/_components/yaml-editor'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { forwardRef, useImperativeHandle } from 'react'; +import { ProviderType, ProviderValueMap } from '../config'; import { ClusterFormData as FormData } from '../config/types'; import schema from '../config/worker-config.json'; import yamlTemplate from '../config/yaml-template'; -const ClusterAdvanceConfig: React.FC<{ action: PageActionType; ref?: any }> = - forwardRef(({ action }, ref) => { - const [form] = Form.useForm(); - const intl = useIntl(); - const editorRef = React.useRef(null); - const [fileContent, setFileContent] = React.useState(''); +const ClusterAdvanceConfig: React.FC<{ + action: PageActionType; + provider: ProviderType; + ref?: any; +}> = forwardRef(({ action, provider }, ref) => { + const [form] = Form.useForm(); + const intl = useIntl(); + const editorRef = React.useRef(null); + const [fileContent, setFileContent] = React.useState(yamlTemplate); - useImperativeHandle(ref, () => ({ - getYamlValue: () => { - return editorRef.current?.getValue(); - }, - setYamlValue: (values: any) => { - console.log('setYamlValue:', values, editorRef.current); - editorRef.current?.setValue(values); - } - })); + useImperativeHandle(ref, () => ({ + getYamlValue: () => { + return editorRef.current?.getValue(); + }, + setYamlValue: (values: any) => { + console.log('setYamlValue:', values, editorRef.current); + editorRef.current?.setValue(values); + } + })); - return ( - <> + return ( + <> + {provider !== ProviderValueMap.DigitalOcean && ( name="server_url" rules={[ @@ -42,34 +47,31 @@ const ClusterAdvanceConfig: React.FC<{ action: PageActionType; ref?: any }> = trim={true} > - - hidden - name="worker_config" - rules={[ - { - required: false, - message: '' - } - ]} - > - - - { - setFileContent(content); - }} - schema={schema} - placeholder={yamlTemplate} - > - - ); - }); + )} + + hidden + name="worker_config" + rules={[ + { + required: false, + message: '' + } + ]} + > + + + { + setFileContent(content); + }} + schema={schema} + > + + ); +}); export default ClusterAdvanceConfig;