diff --git a/src/hooks/use-update-chunk-list.ts b/src/hooks/use-update-chunk-list.ts index 10808cf6..e30127f8 100644 --- a/src/hooks/use-update-chunk-list.ts +++ b/src/hooks/use-update-chunk-list.ts @@ -15,7 +15,10 @@ export function useUpdateChunkedList(options: { events?: EventsType[]; dataList?: any[]; limit?: number; - setDataList: (args: any, opts?: any) => void; + onCreate?: (args: any) => void; + onUpdate?: (args: any) => void; + onDelete?: (args: any) => void; + setDataList?: (args: any, opts?: any) => void; callback?: (args: any) => void; filterFun?: (args: any) => boolean; mapFun?: (args: any) => any; @@ -56,6 +59,7 @@ export function useUpdateChunkedList(options: { collections = data?.collection?.map(options?.mapFun); } const ids: any[] = data?.ids || []; + // CREATE if (data?.type === WatchEventType.CREATE && events.includes('CREATE')) { const newDataList = collections.reduce((acc: any[], item: any) => { @@ -71,11 +75,15 @@ export function useUpdateChunkedList(options: { return acc; }, []); + cacheDataListRef.current = [ ...newDataList, ...cacheDataListRef.current ].slice(0, limit); + + options.onCreate?.(newDataList); } + // DELETE if (data?.type === WatchEventType.DELETE && events.includes('DELETE')) { cacheDataListRef.current = cacheDataListRef.current?.filter( @@ -88,6 +96,7 @@ export function useUpdateChunkedList(options: { deletedIds: [...deletedIdsRef.current] }); } + // UPDATE if (data?.type === WatchEventType.UPDATE && events.includes('UPDATE')) { collections?.forEach((item: any) => { @@ -102,6 +111,7 @@ export function useUpdateChunkedList(options: { updateItem, ...cacheDataListRef.current.slice(0, limit - 1) ]; + options.onCreate?.([updateItem]); } }); } diff --git a/src/locales/en-US/clusters.ts b/src/locales/en-US/clusters.ts index 95c7184d..1f43158f 100644 --- a/src/locales/en-US/clusters.ts +++ b/src/locales/en-US/clusters.ts @@ -88,5 +88,7 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device', 'clusters.button.genToken': 'Need to create a new token? Click here.', - 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.` + 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.`, + 'clusters.addworker.message.success': + '{count} workers have been added to the cluster.' }; diff --git a/src/locales/ja-JP/clusters.ts b/src/locales/ja-JP/clusters.ts index 540396b7..0104b161 100644 --- a/src/locales/ja-JP/clusters.ts +++ b/src/locales/ja-JP/clusters.ts @@ -88,7 +88,9 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device', 'clusters.button.genToken': 'Need to create a new token? Click here.', - 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.` + 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.`, + 'clusters.addworker.message.success': + '{count} workers have been added to the cluster.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -165,4 +167,5 @@ Same applies to the /opt/dtk directory.`, // 71. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', // 72. 'clusters.addworker.cacheVolume.tips': 'If you want to customize the model cache directory, you can specify the path to mount it.', // 73. 'clusters.addworker.cacheVolume.holder': 'e.g. /data/cache (path must start with /)', +// 74. 'clusters.addworker.message.success': '{count} workers have been added to the cluster.' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/clusters.ts b/src/locales/ru-RU/clusters.ts index f36868cd..46ff3f38 100644 --- a/src/locales/ru-RU/clusters.ts +++ b/src/locales/ru-RU/clusters.ts @@ -88,7 +88,9 @@ export default { 'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}', 'clusters.button.genToken': 'Need to create a new token? Click here.', - 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.` + 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.`, + 'clusters.addworker.message.success': + '{count} workers have been added to the cluster.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -96,4 +98,5 @@ export default { // 2. 'clusters.button.genToken': 'Need to create a new token? Click here.', // 3. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', // 4. 'clusters.addworker.cacheVolume.tips': 'If you want to customize the model cache directory, you can specify the path to mount it.', +// 5. 'clusters.addworker.message.success': '{count} workers have been added to the cluster.' // ================================================================ diff --git a/src/locales/zh-CN/clusters.ts b/src/locales/zh-CN/clusters.ts index d360a947..5f6d220c 100644 --- a/src/locales/zh-CN/clusters.ts +++ b/src/locales/zh-CN/clusters.ts @@ -87,5 +87,6 @@ export default { 'clusters.button.genToken': '需要创建令牌?点击这里。', 'clusters.addworker.amdNotes-01': - '如果 /opt/rocm 目录不存在,请创建一个指向已安装 ROCm 路径的符号链接:ln -s /path/to/rocm /opt/rocm。' + '如果 /opt/rocm 目录不存在,请创建一个指向已安装 ROCm 路径的符号链接:ln -s /path/to/rocm /opt/rocm。', + 'clusters.addworker.message.success': '已将 {count} 个工作节点添加到集群中。' }; 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 792eb704..4fd23891 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 @@ -1,4 +1,5 @@ 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 React from 'react'; @@ -56,14 +57,21 @@ const AddWorkerSteps: React.FC = (props) => { onClusterChange } = props || {}; const intl = useIntl(); - const { update, summary, register } = useSummaryStatus(); const [collapseKey, setCollapseKey] = React.useState>( new Set([stepList[0]]) ); + const startWatchRef = React.useRef(false); + const { update, summary, register } = useSummaryStatus(); + const { contextHolder, createModelsChunkRequest } = useAddWorkerMessage({ + startWatch: startWatchRef + }); const onToggle = (open: boolean, key: string) => { setCollapseKey(open ? new Set([key]) : new Set()); + if (key === StepNamesMap.RunCommand && open) { + startWatchRef.current = true; + } }; const handleOnClusterChange = (value: number, row?: any) => { @@ -75,6 +83,10 @@ const AddWorkerSteps: React.FC = (props) => { setCollapseKey(new Set([stepList[0]])); }, [stepList]); + React.useEffect(() => { + createModelsChunkRequest(); + }, []); + return ( = (props) => { )} + {contextHolder} ); }; diff --git a/src/pages/cluster-management/hooks/use-add-worker-message.ts b/src/pages/cluster-management/hooks/use-add-worker-message.ts new file mode 100644 index 00000000..c6e9e6b9 --- /dev/null +++ b/src/pages/cluster-management/hooks/use-add-worker-message.ts @@ -0,0 +1,64 @@ +import useSetChunkRequest from '@/hooks/use-chunk-request'; +import useUpdateChunkedList from '@/hooks/use-update-chunk-list'; +import { useIntl } from '@umijs/max'; +import { message } from 'antd'; +import _ from 'lodash'; +import { useRef } from 'react'; +import { WORKERS_API } from '../../resources/apis'; + +export default function useAddWorkerMessage(params: { + startWatch?: React.RefObject; +}) { + const chunkRequestRef = useRef(null); + const newItemsRef = useRef([]); + const intl = useIntl(); + const [messageApi, contextHolder] = message.useMessage(); + + const showAddWorkerMessage = _.debounce(() => { + if (newItemsRef.current.length > 0) { + const count = newItemsRef.current.length; + messageApi.success( + intl.formatMessage( + { id: 'clusters.addworker.message.success' }, + { count } + ) + ); + newItemsRef.current = []; + } + }, 300); + + const { setChunkRequest } = useSetChunkRequest(); + const { updateChunkedList } = useUpdateChunkedList({ + events: ['CREATE', 'INSERT'], + dataList: [], + onCreate: (newItems: any) => { + if (params.startWatch?.current) { + newItemsRef.current = newItemsRef.current.concat(newItems); + showAddWorkerMessage(); + } + } + }); + + const updateHandler = (list: any) => { + _.each(list, (data: any) => { + updateChunkedList(data); + }); + }; + + const createModelsChunkRequest = async () => { + chunkRequestRef.current?.current?.cancel?.(); + try { + chunkRequestRef.current = setChunkRequest({ + url: WORKERS_API, + handler: updateHandler + }); + } catch (error) { + // ignore + } + }; + + return { + contextHolder, + createModelsChunkRequest + }; +} diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index bd82382b..7a349bf9 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -48,6 +48,7 @@ const Workers: React.FC = () => { } = useTableFetch({ fetchAPI: queryWorkersList, deleteAPI: deleteWorker, + events: ['UPDATE', 'DELETE', 'INSERT'], contentForDelete: 'resources.worker', watch: true, API: WORKERS_API