From 530e8f753742065f2174e33a850a565c70239004 Mon Sep 17 00:00:00 2001 From: jialin Date: Tue, 23 Dec 2025 18:34:41 +0800 Subject: [PATCH] fix: replicas, ready_replicas sorting --- src/atoms/settings.ts | 4 ++- src/atoms/utils/index.ts | 1 + src/hooks/use-table-fetch.ts | 1 + src/layouts/index.tsx | 33 +++++++++++------ src/locales/en-US/clusters.ts | 8 ++--- src/locales/en-US/common.ts | 3 +- src/locales/ja-JP/clusters.ts | 11 +++--- src/locales/ja-JP/common.ts | 6 ++-- src/locales/ru-RU/clusters.ts | 4 +-- src/locales/ru-RU/common.ts | 5 +-- src/locales/zh-CN/clusters.ts | 14 ++++---- src/locales/zh-CN/common.ts | 3 +- .../components/add-worker/summary-data.tsx | 30 ++++++---------- .../dashboard/hooks/use-add-resource.tsx | 36 ++++++++++++------- .../llmodels/hooks/use-models-columns.tsx | 5 ++- src/pages/llmodels/index.tsx | 9 ++++- .../playground/components/ground-images.tsx | 5 ++- .../playground/components/image-edit.tsx | 5 ++- 18 files changed, 106 insertions(+), 77 deletions(-) diff --git a/src/atoms/settings.ts b/src/atoms/settings.ts index b360de49..7bc1c24f 100644 --- a/src/atoms/settings.ts +++ b/src/atoms/settings.ts @@ -8,6 +8,7 @@ type UserSettings = { colorPrimary: string; isDarkTheme: boolean; collapsed: boolean; + hideAddResourceModal?: boolean; }; const defaultSettings: UserSettings = { @@ -15,7 +16,8 @@ const defaultSettings: UserSettings = { mode: 'auto', isDarkTheme: false, colorPrimary: COLOR_PRIMARY, - collapsed: false + collapsed: false, + hideAddResourceModal: false }; export const getStorageUserSettings = () => { diff --git a/src/atoms/utils/index.ts b/src/atoms/utils/index.ts index 63182b33..1cb6deab 100644 --- a/src/atoms/utils/index.ts +++ b/src/atoms/utils/index.ts @@ -9,6 +9,7 @@ export const clearStorageUserSettings = () => { 'userSettings', JSON.stringify({ ...savedSettings, + hideAddResourceModal: false, colorPrimary: undefined }) ); diff --git a/src/hooks/use-table-fetch.ts b/src/hooks/use-table-fetch.ts index 395a2420..40112e01 100644 --- a/src/hooks/use-table-fetch.ts +++ b/src/hooks/use-table-fetch.ts @@ -244,6 +244,7 @@ export default function useTableFetch( setQueryParams((pre: any) => { return { ...pre, + page: 1, sort_by: sortKeys.join(',') }; }); diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index c21a307f..76251db0 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -28,7 +28,7 @@ import { Button, ConfigProvider, Modal, theme } from 'antd'; import 'driver.js/dist/driver.css'; import { useAtom } from 'jotai'; import 'overlayscrollbars/overlayscrollbars.css'; -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { PageContainerInner } from '../pages/_components/page-box'; import Exception from './Exception'; import './Layout.css'; @@ -113,12 +113,6 @@ export default (props: any) => { const { initialize: initialize } = useOverlayScroller({ defer: false }); - const { - setLoadingStatus, - fetchResourceData, - NoResourceModal, - loadingStatus - } = useAddResource(); const [modal, contextHolder] = Modal.useModal(); const { themeData, setUserSettings, userSettings } = useUserSettings(); const [userInfo] = useAtom(userAtom); @@ -128,6 +122,18 @@ export default (props: any) => { const intl = useIntl(); const { clientRoutes } = useAppData(); const [version] = useAtom(GPUStackVersionAtom); + const requestResourceRef = useRef(false); + + const { + setLoadingStatus, + fetchResourceData, + NoResourceModal, + loadingStatus + } = useAddResource({ + onCreate() { + requestResourceRef.current = false; + } + }); const initialInfo = (useModel && useModel('@@initialState')) || { initialState: undefined, @@ -252,15 +258,18 @@ export default (props: any) => { return ; }; - const onPageChange = (route: any) => { + const onPageChange = async (route: any) => { const { location } = history; const { pathname } = location; if ( !CHECK_RESOURCE_PATH.includes(pathname) && - initialState?.currentUser?.is_admin + initialState?.currentUser?.is_admin && + !requestResourceRef.current && + !userSettings.hideAddResourceModal ) { - fetchResourceData(); + requestResourceRef.current = true; + await fetchResourceData(); } initRouteCacheValue(pathname); @@ -294,6 +303,10 @@ export default (props: any) => { }; const onCollapse = (value: boolean) => { + // only trigger by window resize + if (!value) { + return; + } setUserSettings({ ...userSettings, collapsed: value diff --git a/src/locales/en-US/clusters.ts b/src/locales/en-US/clusters.ts index ecdf852b..b1624f39 100644 --- a/src/locales/en-US/clusters.ts +++ b/src/locales/en-US/clusters.ts @@ -72,7 +72,7 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP', 'clusters.addworker.specifyWorkerAddress': 'Specify Worker External Address', 'clusters.addworker.detectWorkerAddress': - 'Specify Worker External Address (default uses Worker IP)', + 'Specify Worker External Address (default to Worker IP)', 'clusters.addworker.externalIP.tips': 'If running in a VPC or private network, please specify the Worker external address reachable by the GPUStack Server.', 'clusters.addworker.enterWorkerIP': 'Enter worker IP', @@ -83,10 +83,10 @@ Same applies to the /opt/dtk directory.`, 'If you want to customize the model cache directory, you can specify the path to mount it.', 'clusters.addworker.configSummary': 'Configuration Summary', 'clusters.addworker.gpuVendor': 'GPU Vendor', - 'clusters.addworker.workerIP': - 'Worker IP {type}', + 'clusters.addworker.workerIP': 'Worker IP', + 'clusters.addworker.workerExternalIP': 'Worker External Address', 'clusters.addworker.notSpecified': 'Not Specified', - 'clusters.addworker.autoDetect': 'Auto-detect', + 'clusters.addworker.autoDetect': 'Auto', 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)', 'clusters.addworker.cacheVolume.holder': diff --git a/src/locales/en-US/common.ts b/src/locales/en-US/common.ts index 89ceab74..35208f1e 100644 --- a/src/locales/en-US/common.ts +++ b/src/locales/en-US/common.ts @@ -265,5 +265,6 @@ export default { 'common.filter.status': 'Filter by status', 'common.form.rule.selectInput': 'Please select or enter a {name}', 'common.tag.experimental': 'Experimental', - 'common.title.example': 'Example' + 'common.title.example': 'Example', + 'common.button.dontshowagain': "Don't show again" }; diff --git a/src/locales/ja-JP/clusters.ts b/src/locales/ja-JP/clusters.ts index 56e300db..4b0eaaf2 100644 --- a/src/locales/ja-JP/clusters.ts +++ b/src/locales/ja-JP/clusters.ts @@ -83,10 +83,10 @@ Same applies to the /opt/dtk directory.`, 'If you want to customize the model cache directory, you can specify the path to mount it.', 'clusters.addworker.configSummary': 'Configuration Summary', 'clusters.addworker.gpuVendor': 'GPU Vendor', - 'clusters.addworker.workerIP': - 'Worker IP {type}', + 'clusters.addworker.workerIP': 'Worker IP', + 'clusters.addworker.workerExternalIP': 'Worker External Address', 'clusters.addworker.notSpecified': 'Not Specified', - 'clusters.addworker.autoDetect': 'Auto-detect', + 'clusters.addworker.autoDetect': 'Auto', 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)', 'clusters.addworker.cacheVolume.holder': @@ -177,9 +177,10 @@ Same applies to the /opt/dtk directory.`, // 61. 'clusters.addworker.extraVolume': 'Additional Volume Mount', // 62. 'clusters.addworker.configSummary': 'Configuration Summary', // 63. 'clusters.addworker.gpuVendor': 'GPU Vendor', -// 64. 'clusters.addworker.workerIP': 'Worker IP {type}', +// 64. 'clusters.addworker.workerIP': 'Worker IP', +// 65. 'clusters.addworker.workerExternalIP': 'Worker External Address', // 65. 'clusters.addworker.notSpecified': 'Not Specified', -// 66. 'clusters.addworker.autoDetect': 'Auto-detect', +// 66. 'clusters.addworker.autoDetect': 'Auto', // 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)' // 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device', // 69. 'clusters.button.genToken': 'Need to create a new token? Click here.', diff --git a/src/locales/ja-JP/common.ts b/src/locales/ja-JP/common.ts index 2a0b740d..84ffa798 100644 --- a/src/locales/ja-JP/common.ts +++ b/src/locales/ja-JP/common.ts @@ -265,7 +265,8 @@ export default { 'common.filter.status': 'Filter by status', 'common.form.rule.selectInput': 'Please select or enter a {name}', 'common.tag.experimental': 'Experimental', - 'common.title.example': 'Example' + 'common.title.example': 'Example', + 'common.button.dontshowagain': "Don't show again" }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -296,5 +297,6 @@ export default { // 25. 'common.filter.status': 'Filter by status' // 26. 'common.form.rule.selectInput': 'Please select or enter a {name}', // 27. 'common.tag.experimental': 'Experimental', -// 28. 'common.title.example': 'Example' +// 28. 'common.title.example': 'Example', +// 29. 'common.button.dontshowagain': "Don't show again" // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/clusters.ts b/src/locales/ru-RU/clusters.ts index fcc006f6..d802f2cc 100644 --- a/src/locales/ru-RU/clusters.ts +++ b/src/locales/ru-RU/clusters.ts @@ -83,8 +83,8 @@ export default { 'If you want to customize the model cache directory, you can specify the path to mount it.', 'clusters.addworker.configSummary': 'Сводка конфигурации', 'clusters.addworker.gpuVendor': 'Производитель GPU', - 'clusters.addworker.workerIP': - 'IP воркера {type}', + 'clusters.addworker.workerIP': 'IP воркера', + 'clusters.addworker.workerExternalIP': 'Внешний адрес воркера', 'clusters.addworker.notSpecified': 'Не указано', 'clusters.addworker.autoDetect': 'Автоопределение', 'clusters.addworker.extraVolume.holder': diff --git a/src/locales/ru-RU/common.ts b/src/locales/ru-RU/common.ts index 994cba05..4cd28f22 100644 --- a/src/locales/ru-RU/common.ts +++ b/src/locales/ru-RU/common.ts @@ -264,9 +264,10 @@ export default { 'common.filter.status': 'Фильтровать по статусу', 'common.form.rule.selectInput': 'Выберите или введите {name}', 'common.tag.experimental': 'Экспериментальный', - 'common.title.example': 'Пример' + 'common.title.example': 'Пример', + 'common.button.dontshowagain': "Don't show again" }; // ========== To-Do: Translate Keys (Remove After Translation) ========== - +// 1. 'common.button.dontshowagain': "Don't show again" // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/clusters.ts b/src/locales/zh-CN/clusters.ts index 8bb0af3d..438022eb 100644 --- a/src/locales/zh-CN/clusters.ts +++ b/src/locales/zh-CN/clusters.ts @@ -66,13 +66,13 @@ export default { 'clusters.addworker.checkEnv': '检查环境', 'clusters.addworker.specifyArgs': '指定参数', 'clusters.addworker.runCommand': '运行指令', - 'clusters.addworker.specifyWorkerIP': '指定 Worker IP', - 'clusters.addworker.detectWorkerIP': '自动检测 Worker IP', - 'clusters.addworker.specifyWorkerAddress': '指定 Worker 外部地址', + 'clusters.addworker.specifyWorkerIP': '指定节点 IP', + 'clusters.addworker.detectWorkerIP': '自动检测节点 IP', + 'clusters.addworker.specifyWorkerAddress': '指定节点外部地址', 'clusters.addworker.detectWorkerAddress': - '指定 Worker 外部地址(默认使用Worker IP)', + '指定节点外部地址(默认使用节点 IP)', 'clusters.addworker.externalIP.tips': - '如运行在 VPC 或私有网络时,请指定 GPUStack Server 可达的 Worker 外部地址。', + '如运行在 VPC 或私有网络时,请指定 GPUStack Server 可达的节点外部地址。', 'clusters.addworker.enterWorkerIP': '输入节点 IP', 'clusters.addworker.enterWorkerIP.error': '请输入节点 IP', 'clusters.addworker.extraVolume': '额外卷挂载', @@ -81,8 +81,8 @@ export default { '如果你想自定义模型缓存目录,可以指定路径进行挂载。', 'clusters.addworker.configSummary': '配置摘要', 'clusters.addworker.gpuVendor': 'GPU 厂商', - 'clusters.addworker.workerIP': - '节点 IP {type}', + 'clusters.addworker.workerIP': '节点 IP', + 'clusters.addworker.workerExternalIP': '节点外部地址', 'clusters.addworker.notSpecified': '未指定', 'clusters.addworker.autoDetect': '自动检测', 'clusters.addworker.extraVolume.holder': diff --git a/src/locales/zh-CN/common.ts b/src/locales/zh-CN/common.ts index b22b5bb2..7b69e4c5 100644 --- a/src/locales/zh-CN/common.ts +++ b/src/locales/zh-CN/common.ts @@ -257,5 +257,6 @@ export default { 'common.filter.status': '按状态筛选', 'common.form.rule.selectInput': '请选择或输入{name}', 'common.tag.experimental': '实验性', - 'common.title.example': '示例' + 'common.title.example': '示例', + 'common.button.dontshowagain': '不再提示' }; diff --git a/src/pages/cluster-management/components/add-worker/summary-data.tsx b/src/pages/cluster-management/components/add-worker/summary-data.tsx index 36818ec9..8d9d3bbc 100644 --- a/src/pages/cluster-management/components/add-worker/summary-data.tsx +++ b/src/pages/cluster-management/components/add-worker/summary-data.tsx @@ -126,16 +126,11 @@ const SummaryData: React.FC = () => { + + {intl.formatMessage({ + id: 'clusters.addworker.workerIP' + })} + } tips={ workerIPConfig.enable @@ -151,16 +146,11 @@ const SummaryData: React.FC = () => { + + {intl.formatMessage({ + id: 'clusters.addworker.workerExternalIP' + })} + } tips={ externalWorkerIPConfig.enable diff --git a/src/pages/dashboard/hooks/use-add-resource.tsx b/src/pages/dashboard/hooks/use-add-resource.tsx index 98667182..ef1f84ee 100644 --- a/src/pages/dashboard/hooks/use-add-resource.tsx +++ b/src/pages/dashboard/hooks/use-add-resource.tsx @@ -2,6 +2,7 @@ import { clusterSessionAtom } from '@/atoms/clusters'; import IconFont from '@/components/icon-font'; import ScrollerModal from '@/components/scroller-modal/index'; import { PageAction } from '@/config'; +import useUserSettings from '@/hooks/use-user-settings'; import useClusterList from '@/pages/cluster-management/hooks/use-cluster-list'; import { useIntl, useNavigate } from '@umijs/max'; import { Button } from 'antd'; @@ -53,14 +54,15 @@ const Content = styled.div` } `; -export default function useAddResource() { +export default function useAddResource(options?: { onCreated?: () => void }) { + const { onCreated } = options || {}; const intl = useIntl(); const navigate = useNavigate(); + const { setUserSettings, userSettings } = useUserSettings(); const [, setClusterSession] = useAtom(clusterSessionAtom); - + const [hideModalTemporarily, setHideModalTemporarily] = useState(false); const { fetchResource, resourceCount, resourceAtom } = useClusterList(); - const [hiddenModal, setHiddenModal] = useState(false); const [loadingStatus, setLoadingStatus] = useState({ loading: false, loadend: false @@ -73,7 +75,6 @@ export default function useAddResource() { }, [resourceAtom, loadingStatus]); const contentInfo = useMemo(() => { - console.log('resourceCount=', resourceCount); if (!resourceCount.cluster_count) { return { title: intl.formatMessage({ id: 'noresult.cluster.title' }), @@ -89,11 +90,12 @@ export default function useAddResource() { }, [resourceCount, intl]); const open: boolean = useMemo(() => { - return isNoResource && !hiddenModal; - }, [isNoResource, hiddenModal]); + return isNoResource && !userSettings.hideAddResourceModal; + }, [isNoResource, userSettings.hideAddResourceModal]); const handleCreate = () => { - setHiddenModal(true); + setHideModalTemporarily(true); + onCreated?.(); if (!resourceCount.cluster_count) { setClusterSession({ firstAddWorker: false, @@ -116,23 +118,33 @@ export default function useAddResource() { }; const handleCancel = () => { - setHiddenModal(true); + setUserSettings({ + ...userSettings, + hideAddResourceModal: true + }); }; const fetchResourceData = async () => { setLoadingStatus({ loading: true, loadend: false }); - setHiddenModal(false); - await fetchResource(); + const { hasClusters, hasWorkers } = await fetchResource(); setLoadingStatus({ loading: false, loadend: true }); + setUserSettings({ + ...userSettings, + hideAddResourceModal: hasClusters && hasWorkers + }); }; const NoResourceModal = ( @@ -145,7 +157,7 @@ export default function useAddResource() {