fix: refresh enable backend state

This commit is contained in:
jialin
2026-02-04 14:58:48 +08:00
parent 05c05419fc
commit 7fd2b43704
14 changed files with 74 additions and 27 deletions
+3
View File
@@ -0,0 +1,3 @@
import { atom } from 'jotai';
export const enabledBackendsAtom = atom<any>(null);
+1
View File
@@ -430,6 +430,7 @@ export default function useTableFetch<T>(
extraStatus, extraStatus,
TABLE_SORT_DIRECTIONS, TABLE_SORT_DIRECTIONS,
debounceFetchData, debounceFetchData,
setDataSource,
setQueryParams, setQueryParams,
handleDelete, handleDelete,
handleDeleteBatch, handleDeleteBatch,
+1 -1
View File
@@ -44,5 +44,5 @@ export default {
'backend.filter.source': 'Filter by source', 'backend.filter.source': 'Filter by source',
'backend.add.custom': 'Custom', 'backend.add.custom': 'Custom',
'backend.add.community': 'Community Backends', 'backend.add.community': 'Community Backends',
'backend.community.title': 'Community Backends Marketplace' 'backend.community.title': 'Community Backend Marketplace'
}; };
+1 -1
View File
@@ -113,7 +113,7 @@ export default {
'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External', 'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack directly.', 'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.',
'clusters.form.setDefault': 'Set as Default', 'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.', 'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available Docker clusters found', 'clusters.addworker.noClusters': 'No available Docker clusters found',
+1 -1
View File
@@ -39,5 +39,5 @@ export default {
'users.status.deactivate': 'Deactivate Account', 'users.status.deactivate': 'Deactivate Account',
'users.status.inactiveAccount': 'Inactive Account', 'users.status.inactiveAccount': 'Inactive Account',
'users.login.getInitialPassword': 'users.login.getInitialPassword':
'Run the following command on your server to retrieve the initial admin password.' 'Run the following command on your GPUStack Server to retrieve the initial admin password.'
}; };
+1 -1
View File
@@ -44,5 +44,5 @@ export default {
'backend.filter.source': 'Filter by source', 'backend.filter.source': 'Filter by source',
'backend.add.custom': 'Custom', 'backend.add.custom': 'Custom',
'backend.add.community': 'Community Backends', 'backend.add.community': 'Community Backends',
'backend.community.title': 'Community Backends Marketplace' 'backend.community.title': 'Community Backend Marketplace'
}; };
+2 -2
View File
@@ -113,7 +113,7 @@ export default {
'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External', 'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack directly.', 'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.',
'clusters.form.setDefault': 'Set as Default', 'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.', 'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available Docker clusters found', 'clusters.addworker.noClusters': 'No available Docker clusters found',
@@ -217,7 +217,7 @@ export default {
// 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.', // 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.',
// 79. 'clusters.table.ip.internal': 'Internal', // 79. 'clusters.table.ip.internal': 'Internal',
// 80. 'clusters.table.ip.external': 'External', // 80. 'clusters.table.ip.external': 'External',
// 81. 'clusters.form.serverUrl.tips': 'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack directly.', // 81. 'clusters.form.serverUrl.tips': 'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.',
// 82. 'clusters.addworker.externalIP.tips': 'Specify an external IP if the worker is in a VPC or private network.', // 82. 'clusters.addworker.externalIP.tips': 'Specify an external IP if the worker is in a VPC or private network.',
// 83. 'clusters.form.setDefault': 'Set as Default', // 83. 'clusters.form.setDefault': 'Set as Default',
// 84. 'clusters.form.setDefault.tips': 'Default for deployment', // 84. 'clusters.form.setDefault.tips': 'Default for deployment',
+2 -2
View File
@@ -40,7 +40,7 @@ export default {
'users.status.deactivate': 'Deactivate Account', 'users.status.deactivate': 'Deactivate Account',
'users.status.inactiveAccount': 'Inactive Account', 'users.status.inactiveAccount': 'Inactive Account',
'users.login.getInitialPassword': 'users.login.getInitialPassword':
'Run the following command on your server to retrieve the initial admin password.' 'Run the following command on your GPUStack Server to retrieve the initial admin password.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -48,5 +48,5 @@ export default {
// 2. 'users.status.activate': 'Activate Account', // 2. 'users.status.activate': 'Activate Account',
// 3. 'users.status.deactivate': 'Deactivate Account', // 3. 'users.status.deactivate': 'Deactivate Account',
// 4. 'users.status.inactiveAccount': 'Inactive Account', // 4. 'users.status.inactiveAccount': 'Inactive Account',
// 5. 'users.login.getInitialPassword': 'Run the following command on your server to retrieve the initial admin password.' // 5. 'users.login.getInitialPassword': 'Run the following command on your GPUStack Server to retrieve the initial admin password.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+2 -2
View File
@@ -44,7 +44,7 @@ export default {
'backend.filter.source': 'Filter by source', 'backend.filter.source': 'Filter by source',
'backend.add.custom': 'Custom', 'backend.add.custom': 'Custom',
'backend.add.community': 'Community Backends', 'backend.add.community': 'Community Backends',
'backend.community.title': 'Community Backends Marketplace' 'backend.community.title': 'Community Backend Marketplace'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -53,5 +53,5 @@ export default {
// 3. 'backend.filter.source': 'Filter by source', // 3. 'backend.filter.source': 'Filter by source',
// 4. 'backend.add.custom': 'Custom', // 4. 'backend.add.custom': 'Custom',
// 5. 'backend.add.community': 'Community Backends', // 5. 'backend.add.community': 'Community Backends',
// 6. 'backend.community.title': 'Community Backends Marketplace' // 6. 'backend.community.title': 'Community Backend Marketplace'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+1 -1
View File
@@ -113,7 +113,7 @@ export default {
'clusters.table.ip.internal': 'Внутренний', 'clusters.table.ip.internal': 'Внутренний',
'clusters.table.ip.external': 'Внешний', 'clusters.table.ip.external': 'Внешний',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Если рабочий узел не может напрямую получить доступ к GPUStack, укажите внешний URL службы GPUStack.', 'Если рабочий узел не может напрямую получить доступ к GPUStack Server, укажите внешний URL службы GPUStack Server.',
'clusters.form.setDefault': 'Установить по умолчанию', 'clusters.form.setDefault': 'Установить по умолчанию',
'clusters.form.setDefault.tips': 'clusters.form.setDefault.tips':
'Использовать по умолчанию для развертывания.', 'Использовать по умолчанию для развертывания.',
+2 -2
View File
@@ -102,7 +102,7 @@ export default {
'已将 {count} 个新节点添加到集群中。', '已将 {count} 个新节点添加到集群中。',
'clusters.addworker.message.success_multiple': 'clusters.addworker.message.success_multiple':
'已将 {count} 个新节点添加到集群中。', '已将 {count} 个新节点添加到集群中。',
'clusters.create.serverUrl': 'GPUStack 服务地址', 'clusters.create.serverUrl': 'GPUStack Server 节点地址',
'clusters.create.workerConfig': '节点配置', 'clusters.create.workerConfig': '节点配置',
'clusters.addworker.containerName': '节点容器名称', 'clusters.addworker.containerName': '节点容器名称',
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。', 'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
@@ -111,7 +111,7 @@ export default {
'clusters.table.ip.internal': '内', 'clusters.table.ip.internal': '内',
'clusters.table.ip.external': '外', 'clusters.table.ip.external': '外',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'如果节点无法直接访问 GPUStack,则指定一个可访问的外部 GPUStack 服务地址。', '如果节点无法直接访问 GPUStack Server,则指定一个可访问的外部 GPUStack Server 地址。',
'clusters.form.setDefault': '设为默认', 'clusters.form.setDefault': '设为默认',
'clusters.form.setDefault.tips': '部署时的默认集群。', 'clusters.form.setDefault.tips': '部署时的默认集群。',
'clusters.addworker.noClusters': '无可用的 Docker 集群', 'clusters.addworker.noClusters': '无可用的 Docker 集群',
+1 -1
View File
@@ -258,7 +258,7 @@ export default {
'models.form.backendVersions.tips': `如需使用更多版本,请前往{link}页面并编辑对应的后端以添加版本。`, 'models.form.backendVersions.tips': `如需使用更多版本,请前往{link}页面并编辑对应的后端以添加版本。`,
'models.catalog.nogpus.tips': '所选集群中没有兼容该模型的 GPU。', 'models.catalog.nogpus.tips': '所选集群中没有兼容该模型的 GPU。',
'models.form.modelfile.notfound': 'models.form.modelfile.notfound':
'你指定的模型文件路径在 GPUStack 服务器上不存在。建议在 GPUStack 服务器和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。', '你指定的模型文件路径在 GPUStack Server 节点上不存在。建议在 GPUStack Server 节点和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。',
'models.form.readyWorkers': '节点就绪', 'models.form.readyWorkers': '节点就绪',
'models.form.maxContextLength': '最大上下文长度', 'models.form.maxContextLength': '最大上下文长度',
'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用', 'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用',
@@ -1,8 +1,10 @@
import { enabledBackendsAtom } from '@/atoms/backend';
import GSDrawer from '@/components/scroller-modal/gs-drawer'; import GSDrawer from '@/components/scroller-modal/gs-drawer';
import ColumnWrapper from '@/pages/_components/column-wrapper'; import ColumnWrapper from '@/pages/_components/column-wrapper';
import Separator from '@/pages/llmodels/components/separator'; import Separator from '@/pages/llmodels/components/separator';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import React from 'react'; import { useAtom } from 'jotai';
import React, { useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { ListItem } from '../config/types'; import { ListItem } from '../config/types';
import useEnableBackend from '../services/use-enable-backend'; import useEnableBackend from '../services/use-enable-backend';
@@ -23,6 +25,7 @@ const AddCommunityModal: React.FC<{
const intl = useIntl(); const intl = useIntl();
const { handleEnableBackend } = useEnableBackend(); const { handleEnableBackend } = useEnableBackend();
const [currentData, setCurrentData] = React.useState<any>(null); const [currentData, setCurrentData] = React.useState<any>(null);
const [, setEnabledBackendAtom] = useAtom(enabledBackendsAtom);
const handleOnEnable = async (checked: boolean, data: ListItem) => { const handleOnEnable = async (checked: boolean, data: ListItem) => {
await handleEnableBackend({ await handleEnableBackend({
@@ -32,12 +35,23 @@ const AddCommunityModal: React.FC<{
enabled: checked enabled: checked
} }
}); });
setEnabledBackendAtom({
...data,
enabled: checked
});
onRefresh?.(); onRefresh?.();
}; };
const handleOnClick = (data: ListItem) => { const handleOnClick = (data: ListItem) => {
setCurrentData(data); setCurrentData(data);
}; };
useEffect(() => {
if (!open) {
setEnabledBackendAtom(null);
}
}, [open]);
return ( return (
<GSDrawer <GSDrawer
open={open} open={open}
@@ -1,9 +1,11 @@
import { enabledBackendsAtom } from '@/atoms/backend';
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import ThemeTag from '@/components/tags-wrapper/theme-tag'; import ThemeTag from '@/components/tags-wrapper/theme-tag';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Input, Tooltip } from 'antd'; import { Input, Tooltip } from 'antd';
import { useAtom } from 'jotai';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -28,19 +30,46 @@ const CommunityBackends: React.FC<{
onClick: (data: ListItem) => void; onClick: (data: ListItem) => void;
}> = ({ onEnable, onClick }) => { }> = ({ onEnable, onClick }) => {
const intl = useIntl(); const intl = useIntl();
const [enabledBackendAtom] = useAtom(enabledBackendsAtom);
const [currentData, setCurrentData] = React.useState<ListItem | null>(null); const [currentData, setCurrentData] = React.useState<ListItem | null>(null);
const { dataSource, loadMore, queryParams, handleSearch, handleNameChange } = const {
useTableFetch<ListItem>({ dataSource,
fetchAPI: queryBackendsList, loadMore,
API: INFERENCE_BACKEND_API, queryParams,
watch: false, handleSearch,
isInfiniteScroll: true, setDataSource,
contentForDelete: 'backends.title', handleNameChange
defaultQueryParams: { } = useTableFetch<ListItem>({
perPage: 24, fetchAPI: queryBackendsList,
community: 1 API: INFERENCE_BACKEND_API,
} watch: false,
}); isInfiniteScroll: true,
contentForDelete: 'backends.title',
defaultQueryParams: {
perPage: 24,
community: 1
}
});
useEffect(() => {
if (enabledBackendAtom) {
setDataSource((prev) => {
const newDataList = prev.dataList.map((item) => {
if (item.id === enabledBackendAtom.id) {
return {
...item,
enabled: enabledBackendAtom.enabled
};
}
return item;
});
return {
...prev,
dataList: newDataList
};
});
}
}, [enabledBackendAtom]);
const handleOnClickItem = (data: ListItem) => { const handleOnClickItem = (data: ListItem) => {
onClick(data); onClick(data);