fix: replicas, ready_replicas sorting

This commit is contained in:
jialin
2025-12-23 18:35:33 +08:00
parent 9e019a98f6
commit 530e8f7537
18 changed files with 106 additions and 77 deletions
+3 -1
View File
@@ -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 = () => {
+1
View File
@@ -9,6 +9,7 @@ export const clearStorageUserSettings = () => {
'userSettings',
JSON.stringify({
...savedSettings,
hideAddResourceModal: false,
colorPrimary: undefined
})
);
+1
View File
@@ -244,6 +244,7 @@ export default function useTableFetch<T>(
setQueryParams((pre: any) => {
return {
...pre,
page: 1,
sort_by: sortKeys.join(',')
};
});
+23 -10
View File
@@ -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<boolean>(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 <SiderMenu {...menuProps}></SiderMenu>;
};
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
+4 -4
View File
@@ -72,7 +72,7 @@ Same applies to the <span class="bold-text">/opt/dtk</span> 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 <span class="bold-text">/opt/dtk</span> 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 <span class="text-tertiary">{type}</span>',
'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':
+2 -1
View File
@@ -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"
};
+6 -5
View File
@@ -83,10 +83,10 @@ Same applies to the <span class="bold-text">/opt/dtk</span> 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 <span class="text-tertiary">{type}</span>',
'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 <span class="bold-text">/opt/dtk</span> 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 <span class="text-tertiary">{type}</span>',
// 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 <a href="{link}" target="_blank">here</a>.',
+4 -2
View File
@@ -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 ==========
+2 -2
View File
@@ -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 воркера <span class="text-tertiary">{type}</span>',
'clusters.addworker.workerIP': 'IP воркера',
'clusters.addworker.workerExternalIP': 'Внешний адрес воркера',
'clusters.addworker.notSpecified': 'Не указано',
'clusters.addworker.autoDetect': 'Автоопределение',
'clusters.addworker.extraVolume.holder':
+3 -2
View File
@@ -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 ==========
+7 -7
View File
@@ -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 <span class="text-tertiary">{type}</span>',
'clusters.addworker.workerIP': '节点 IP',
'clusters.addworker.workerExternalIP': '节点外部地址',
'clusters.addworker.notSpecified': '未指定',
'clusters.addworker.autoDetect': '自动检测',
'clusters.addworker.extraVolume.holder':
+2 -1
View File
@@ -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': '不再提示'
};
@@ -126,16 +126,11 @@ const SummaryData: React.FC = () => {
<DataItem
label={
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage(
{ id: 'clusters.addworker.workerIP' },
{
type: `(${intl.formatMessage({ id: 'clusters.table.ip.internal' })})`
}
)
}}
></span>
<span>
{intl.formatMessage({
id: 'clusters.addworker.workerIP'
})}
</span>
}
tips={
workerIPConfig.enable
@@ -151,16 +146,11 @@ const SummaryData: React.FC = () => {
<DataItem
label={
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage(
{ id: 'clusters.addworker.workerIP' },
{
type: `(${intl.formatMessage({ id: 'clusters.table.ip.external' })})`
}
)
}}
></span>
<span>
{intl.formatMessage({
id: 'clusters.addworker.workerExternalIP'
})}
</span>
}
tips={
externalWorkerIPConfig.enable
+24 -12
View File
@@ -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 = (
<ScrollerModal
open={open}
open={open && !hideModalTemporarily}
footer={null}
maskClosable={false}
keyboard={false}
closeIcon={null}
destroyOnHidden={false}
style={{
top: '25%'
}}
onCancel={handleCancel}
>
<Content>
@@ -145,7 +157,7 @@ export default function useAddResource() {
</div>
<div className="btn-wrapper">
<Button onClick={handleCancel} type="default" style={{ flex: 1 }}>
{intl.formatMessage({ id: 'clusters.create.skipfornow' })}
{intl.formatMessage({ id: 'common.button.dontshowagain' })}
</Button>
<Button onClick={handleCreate} type="primary" style={{ flex: 1 }}>
{contentInfo.btnText}
@@ -105,9 +105,8 @@ const useModelsColumns = ({
<QuestionCircleOutlined className="m-l-5" />
</Tooltip>
),
dataIndex: 'ready_replicas',
key: 'ready_replicas',
dataField: 'replicas',
dataIndex: 'replicas',
key: 'replicas',
align: 'left',
sorter: tableSorter(4),
span: 4,
+8 -1
View File
@@ -316,7 +316,14 @@ const Models: React.FC = () => {
};
const handleOnSortChange = (order: TableOrder | Array<TableOrder>) => {
const sortKeys = handleMultiSortChange(order);
let orderList = Array.isArray(order) ? order : [order];
if (orderList[0].columnKey === 'replicas') {
orderList.push({
columnKey: 'ready_replicas',
order: orderList[0].order
});
}
const sortKeys = handleMultiSortChange(orderList);
setQueryParams((pre: any) => {
return {
...pre,
@@ -142,9 +142,8 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
const params = {
..._.omitBy(finalParameters, (value: string) => !value),
seed:
parameters.seed || parameters.random_seed
? generateRandomNumber()
: null,
parameters.seed ||
(parameters.random_seed ? generateRandomNumber() : null),
stream: false,
prompt: currentPrompt
};
@@ -167,9 +167,8 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
const params = {
..._.omitBy(finalParameters, (value: string) => !value),
seed:
parameters.seed || parameters.random_seed
? generateRandomNumber()
: null,
parameters.seed ||
(parameters.random_seed ? generateRandomNumber() : null),
stream: false,
prompt: currentPrompt
};