fix: show worker added message
This commit is contained in:
@@ -14,7 +14,6 @@ type EventsType = 'CREATE' | 'UPDATE' | 'DELETE' | 'INSERT';
|
|||||||
export function useUpdateChunkedList(options: {
|
export function useUpdateChunkedList(options: {
|
||||||
events?: EventsType[];
|
events?: EventsType[];
|
||||||
dataList?: any[];
|
dataList?: any[];
|
||||||
triggerAt?: React.MutableRefObject<number>;
|
|
||||||
limit?: number;
|
limit?: number;
|
||||||
onCreate?: (args: any) => void;
|
onCreate?: (args: any) => void;
|
||||||
onUpdate?: (args: any) => void;
|
onUpdate?: (args: any) => void;
|
||||||
@@ -24,9 +23,9 @@ export function useUpdateChunkedList(options: {
|
|||||||
filterFun?: (args: any) => boolean;
|
filterFun?: (args: any) => boolean;
|
||||||
mapFun?: (args: any) => any;
|
mapFun?: (args: any) => any;
|
||||||
computedID?: (d: object) => string;
|
computedID?: (d: object) => string;
|
||||||
|
isNewItem?: (item: any) => boolean;
|
||||||
}) {
|
}) {
|
||||||
const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'], triggerAt } =
|
const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'] } = options;
|
||||||
options;
|
|
||||||
const deletedIdsRef = useRef<Set<number | string>>(new Set());
|
const deletedIdsRef = useRef<Set<number | string>>(new Set());
|
||||||
const cacheDataListRef = useRef<any[]>(options.dataList || []);
|
const cacheDataListRef = useRef<any[]>(options.dataList || []);
|
||||||
const timerRef = useRef<any>(null);
|
const timerRef = useRef<any>(null);
|
||||||
@@ -71,17 +70,14 @@ export function useUpdateChunkedList(options: {
|
|||||||
(sItem: any) => sItem.id === item.id
|
(sItem: any) => sItem.id === item.id
|
||||||
);
|
);
|
||||||
const updateItem = { ...item };
|
const updateItem = { ...item };
|
||||||
if (updateIndex === -1 && !triggerAt?.current) {
|
if (updateIndex === -1) {
|
||||||
acc.push(updateItem);
|
acc.push(updateItem);
|
||||||
} else if (!triggerAt?.current) {
|
} else {
|
||||||
cacheDataListRef.current[updateIndex] = updateItem;
|
cacheDataListRef.current[updateIndex] = updateItem;
|
||||||
}
|
}
|
||||||
|
// only push items created after the watch started
|
||||||
|
|
||||||
// TODO: only push items created after triggerAt
|
if (options.isNewItem?.(item)) {
|
||||||
if (
|
|
||||||
triggerAt?.current &&
|
|
||||||
Date.parse(item.created_at) >= triggerAt.current
|
|
||||||
) {
|
|
||||||
latestCreateList.push(updateItem);
|
latestCreateList.push(updateItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +99,8 @@ export function useUpdateChunkedList(options: {
|
|||||||
cacheDataListRef.current = cacheDataListRef.current?.filter(
|
cacheDataListRef.current = cacheDataListRef.current?.filter(
|
||||||
(item: any) => {
|
(item: any) => {
|
||||||
// collect deleted items
|
// collect deleted items
|
||||||
if (triggerAt?.current) {
|
if (ids?.includes(item.id) && !options.isNewItem?.(item)) {
|
||||||
if (ids?.includes(item.id)) {
|
deletedList.push(item);
|
||||||
deletedList.push(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return !ids?.includes(item.id);
|
return !ids?.includes(item.id);
|
||||||
}
|
}
|
||||||
@@ -133,10 +127,8 @@ export function useUpdateChunkedList(options: {
|
|||||||
updateItem,
|
updateItem,
|
||||||
...cacheDataListRef.current.slice(0, limit - 1)
|
...cacheDataListRef.current.slice(0, limit - 1)
|
||||||
];
|
];
|
||||||
if (options.onUpdate && triggerAt?.current) {
|
if (options.onUpdate && options.isNewItem?.(item)) {
|
||||||
if (Date.parse(item.created_at) >= triggerAt.current) {
|
options.onUpdate?.([updateItem]);
|
||||||
options.onUpdate?.([updateItem]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -396,7 +396,6 @@ export default (props: any) => {
|
|||||||
)}
|
)}
|
||||||
onCollapse={onCollapse}
|
onCollapse={onCollapse}
|
||||||
onMenuHeaderClick={onMenuHeaderClick}
|
onMenuHeaderClick={onMenuHeaderClick}
|
||||||
menuHeaderRender={renderMenuHeader}
|
|
||||||
collapsed={userSettings.collapsed}
|
collapsed={userSettings.collapsed}
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
formatMessage={formatMessage}
|
formatMessage={formatMessage}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
|||||||
);
|
);
|
||||||
const { update, summary, register } = useSummaryStatus();
|
const { update, summary, register } = useSummaryStatus();
|
||||||
const { addedCount, createModelsChunkRequest } = useAddWorkerMessage();
|
const { addedCount, createModelsChunkRequest } = useAddWorkerMessage();
|
||||||
|
console.log('addedCount=========', addedCount);
|
||||||
|
|
||||||
const onToggle = (open: boolean, key: string) => {
|
const onToggle = (open: boolean, key: string) => {
|
||||||
setCollapseKey(open ? new Set([key]) : new Set());
|
setCollapseKey(open ? new Set([key]) : new Set());
|
||||||
@@ -81,6 +82,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
|||||||
setCollapseKey(new Set([stepList[0]]));
|
setCollapseKey(new Set([stepList[0]]));
|
||||||
}, [stepList]);
|
}, [stepList]);
|
||||||
|
|
||||||
|
console.log('actionSource=========', actionSource, registrationInfo);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// this effect is only triggered when used in cluster create page inner
|
// this effect is only triggered when used in cluster create page inner
|
||||||
if (actionSource === 'page' && registrationInfo?.cluster_id) {
|
if (actionSource === 'page' && registrationInfo?.cluster_id) {
|
||||||
@@ -164,6 +166,7 @@ const AddWorkerSteps: React.FC<AddWorkerProps> = (props) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{actionSource === 'modal' && (
|
{actionSource === 'modal' && (
|
||||||
|
// show in cluster create page inner
|
||||||
<AddedMessage addedCount={addedCount}></AddedMessage>
|
<AddedMessage addedCount={addedCount}></AddedMessage>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleOnClusterChange = async (value: number, row?: any) => {
|
const handleOnClusterChange = async (value: number, row?: any) => {
|
||||||
|
console.log('handleOnClusterChange value, row=========', value, row);
|
||||||
try {
|
try {
|
||||||
createModelsChunkRequest({ cluster_id: value });
|
createModelsChunkRequest({ cluster_id: value });
|
||||||
axiosTokenRef.current?.cancel?.();
|
axiosTokenRef.current?.cancel?.();
|
||||||
@@ -121,6 +122,8 @@ const AddWorker: React.FC<AddWorkerProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('addedCount 1========', addedCount);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GSDrawer
|
<GSDrawer
|
||||||
title={title}
|
title={title}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { workerAddedCountAtom } from '@/atoms/clusters';
|
import { workerAddedCountAtom } from '@/atoms/clusters';
|
||||||
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
||||||
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
|
import useUpdateChunkedList from '@/hooks/use-update-chunk-list';
|
||||||
|
import useQueryWorkerList from '@/pages/resources/services/use-query-worker-list';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import qs from 'query-string';
|
import qs from 'query-string';
|
||||||
@@ -13,7 +14,31 @@ export default function useAddWorkerMessage() {
|
|||||||
const [addedCount, setAddedCount] = useState(0);
|
const [addedCount, setAddedCount] = useState(0);
|
||||||
const timerRef = useRef<any>(null);
|
const timerRef = useRef<any>(null);
|
||||||
const triggerAtRef = useRef<number>(0);
|
const triggerAtRef = useRef<number>(0);
|
||||||
|
const existingIdsRef = useRef<Set<string | number>>(new Set());
|
||||||
|
const snapshotReceivedRef = useRef<boolean>(false);
|
||||||
const [, setWorkerAddedCount] = useAtom(workerAddedCountAtom);
|
const [, setWorkerAddedCount] = useAtom(workerAddedCountAtom);
|
||||||
|
// fetchData auto-cancels any in-flight request on each call and on unmount,
|
||||||
|
// so a stale seed from a previous open/cluster can't leak in
|
||||||
|
const { fetchData: fetchWorkerList, cancelRequest: cancelWorkerListRequest } =
|
||||||
|
useQueryWorkerList();
|
||||||
|
|
||||||
|
const isNewWorker = (item: any) => {
|
||||||
|
console.log(
|
||||||
|
'isNewWorker item:',
|
||||||
|
item,
|
||||||
|
existingIdsRef.current,
|
||||||
|
snapshotReceivedRef.current
|
||||||
|
);
|
||||||
|
if (item?.id == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (existingIdsRef.current.has(item.id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
existingIdsRef.current.add(item.id);
|
||||||
|
// before the full snapshot has been received, every item is pre-existing
|
||||||
|
return snapshotReceivedRef.current;
|
||||||
|
};
|
||||||
|
|
||||||
const updateAddedCount = (count: number) => {
|
const updateAddedCount = (count: number) => {
|
||||||
setAddedCount(count);
|
setAddedCount(count);
|
||||||
@@ -30,8 +55,9 @@ export default function useAddWorkerMessage() {
|
|||||||
const { updateChunkedList } = useUpdateChunkedList({
|
const { updateChunkedList } = useUpdateChunkedList({
|
||||||
events: ['CREATE', 'INSERT'],
|
events: ['CREATE', 'INSERT'],
|
||||||
dataList: [],
|
dataList: [],
|
||||||
triggerAt: triggerAtRef,
|
isNewItem: isNewWorker,
|
||||||
onCreate: (newItems: any) => {
|
onCreate: (newItems: any) => {
|
||||||
|
console.log('onCreate newItems:', newItems, triggerAtRef.current);
|
||||||
if (triggerAtRef.current) {
|
if (triggerAtRef.current) {
|
||||||
newItemsRef.current = newItemsRef.current.concat(newItems);
|
newItemsRef.current = newItemsRef.current.concat(newItems);
|
||||||
showAddWorkerMessage();
|
showAddWorkerMessage();
|
||||||
@@ -49,6 +75,27 @@ export default function useAddWorkerMessage() {
|
|||||||
_.each(list, (data: any) => {
|
_.each(list, (data: any) => {
|
||||||
updateChunkedList(data);
|
updateChunkedList(data);
|
||||||
});
|
});
|
||||||
|
// fallback: if seeding the baseline failed, the first watch chunk marks the
|
||||||
|
// snapshot as received (only reliable when there is at least one worker)
|
||||||
|
snapshotReceivedRef.current = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Seed the baseline set of already-existing worker ids via REST before the
|
||||||
|
// watch starts. Relying on the watch's first chunk fails when a cluster has
|
||||||
|
// zero workers (no CREATE event is sent, so the snapshot flag never flips and
|
||||||
|
// genuinely new workers get misclassified as pre-existing).
|
||||||
|
const seedExistingWorkers = async (params: Record<string, any>) => {
|
||||||
|
try {
|
||||||
|
const items = await fetchWorkerList({ ...params, page: -1 } as any);
|
||||||
|
(items || []).forEach((item: any) => {
|
||||||
|
if (item?.id != null) {
|
||||||
|
existingIdsRef.current.add(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
snapshotReceivedRef.current = true;
|
||||||
|
} catch (error) {
|
||||||
|
// ignore: fall back to the first watch chunk (see updateHandler)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetAddedCount = () => {
|
const resetAddedCount = () => {
|
||||||
@@ -56,11 +103,18 @@ export default function useAddWorkerMessage() {
|
|||||||
chunkRequestRef.current?.current?.cancel?.();
|
chunkRequestRef.current?.current?.cancel?.();
|
||||||
newItemsRef.current = [];
|
newItemsRef.current = [];
|
||||||
triggerAtRef.current = 0;
|
triggerAtRef.current = 0;
|
||||||
|
existingIdsRef.current = new Set();
|
||||||
|
snapshotReceivedRef.current = false;
|
||||||
|
// cancel any in-flight seed request
|
||||||
|
cancelWorkerListRequest();
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createModelsChunkRequest = async (params = {}) => {
|
const createModelsChunkRequest = async (params = {}) => {
|
||||||
resetAddedCount();
|
resetAddedCount();
|
||||||
|
// seed the baseline before watching so new workers are detected even when
|
||||||
|
// the cluster currently has zero workers
|
||||||
|
await seedExistingWorkers(params);
|
||||||
try {
|
try {
|
||||||
chunkRequestRef.current = setChunkRequest({
|
chunkRequestRef.current = setChunkRequest({
|
||||||
url: `${WORKERS_API}?${qs.stringify(_.pickBy(params, (val: any) => !!val))}`,
|
url: `${WORKERS_API}?${qs.stringify(_.pickBy(params, (val: any) => !!val))}`,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useIntl } from '@umijs/max';
|
|||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { ConfigProvider, Table } from 'antd';
|
import { ConfigProvider, Table } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { forwardRef, useImperativeHandle } from 'react';
|
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||||
import {
|
import {
|
||||||
deleteModelInstance,
|
deleteModelInstance,
|
||||||
MODEL_INSTANCE_API,
|
MODEL_INSTANCE_API,
|
||||||
@@ -16,6 +16,7 @@ import ViewLogsModal from '../components/view-logs-modal';
|
|||||||
import { useDeploymentsContext } from '../config/deploments-context';
|
import { useDeploymentsContext } from '../config/deploments-context';
|
||||||
import { ModelInstanceListItem as ListItem } from '../config/types';
|
import { ModelInstanceListItem as ListItem } from '../config/types';
|
||||||
import useViewInstanceLogs from '../hooks/use-view-instance-logs';
|
import useViewInstanceLogs from '../hooks/use-view-instance-logs';
|
||||||
|
import useQueryModelList from '../services/use-query-model-list';
|
||||||
import LeftFilters from './left-filters';
|
import LeftFilters from './left-filters';
|
||||||
import useInstanceColumns from './use-instance-columns';
|
import useInstanceColumns from './use-instance-columns';
|
||||||
|
|
||||||
@@ -44,10 +45,15 @@ const InstanceView = forwardRef((props, ref) => {
|
|||||||
contentForDelete: 'menu.models.instances'
|
contentForDelete: 'menu.models.instances'
|
||||||
});
|
});
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { dataList: modelList, fetchData: fetchModelList } =
|
||||||
|
useQueryModelList();
|
||||||
const { clusterList, workerList } = useDeploymentsContext();
|
const { clusterList, workerList } = useDeploymentsContext();
|
||||||
const { openViewLogsModal, openViewLogsModalStatus, closeViewLogsModal } =
|
const { openViewLogsModal, openViewLogsModalStatus, closeViewLogsModal } =
|
||||||
useViewInstanceLogs();
|
useViewInstanceLogs();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchModelList({ page: -1 });
|
||||||
|
}, []);
|
||||||
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {
|
const handleSelect = useMemoizedFn((val: any, row: ListItem) => {
|
||||||
if (val === 'delete') {
|
if (val === 'delete') {
|
||||||
handleDelete(row, {
|
handleDelete(row, {
|
||||||
@@ -119,6 +125,7 @@ const InstanceView = forwardRef((props, ref) => {
|
|||||||
const columns = useInstanceColumns({
|
const columns = useInstanceColumns({
|
||||||
handleSelect,
|
handleSelect,
|
||||||
clusterList,
|
clusterList,
|
||||||
|
modelList,
|
||||||
workerList
|
workerList
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import InstanceStatusCell from '../components/instance-cells/instance-status-cel
|
|||||||
import NameCell, {
|
import NameCell, {
|
||||||
NameCellProps
|
NameCellProps
|
||||||
} from '../components/instance-cells/name-cell';
|
} from '../components/instance-cells/name-cell';
|
||||||
import { ModelInstanceListItem as ListItem } from '../config/types';
|
import {
|
||||||
|
ModelInstanceListItem as ListItem,
|
||||||
|
ListItem as ModelListItem
|
||||||
|
} from '../config/types';
|
||||||
import { calcTotalVram } from '../utils';
|
import { calcTotalVram } from '../utils';
|
||||||
const WorkerInfoContent: React.FC<NameCellProps> = ({ record, modelData }) => {
|
const WorkerInfoContent: React.FC<NameCellProps> = ({ record, modelData }) => {
|
||||||
let workerIp = '-';
|
let workerIp = '-';
|
||||||
@@ -48,6 +51,7 @@ const WorkerInfoContent: React.FC<NameCellProps> = ({ record, modelData }) => {
|
|||||||
|
|
||||||
const useInstancesColumns = (options: {
|
const useInstancesColumns = (options: {
|
||||||
workerList: workerListItem[];
|
workerList: workerListItem[];
|
||||||
|
modelList: ModelListItem[];
|
||||||
clusterList: Global.BaseOption<
|
clusterList: Global.BaseOption<
|
||||||
number,
|
number,
|
||||||
{
|
{
|
||||||
@@ -60,7 +64,8 @@ const useInstancesColumns = (options: {
|
|||||||
onCellClick?: (record: ListItem, dataIndex: string) => void;
|
onCellClick?: (record: ListItem, dataIndex: string) => void;
|
||||||
}): ColumnsType<ListItem> => {
|
}): ColumnsType<ListItem> => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { workerList, clusterList, handleSelect, onCellClick } = options;
|
const { workerList, clusterList, modelList, handleSelect, onCellClick } =
|
||||||
|
options;
|
||||||
|
|
||||||
const renderWorkerCell = (text: number, record: ListItem) => {
|
const renderWorkerCell = (text: number, record: ListItem) => {
|
||||||
if (text) {
|
if (text) {
|
||||||
@@ -144,7 +149,7 @@ const useInstancesColumns = (options: {
|
|||||||
width: 160,
|
width: 160,
|
||||||
render: (text: string, record: ListItem) => (
|
render: (text: string, record: ListItem) => (
|
||||||
<span style={{ gap: 4 }} className="flex-center">
|
<span style={{ gap: 4 }} className="flex-center">
|
||||||
<InstanceStatusCell record={record} onSelect={() => {}} />
|
<InstanceStatusCell record={record} onSelect={handleSelect} />
|
||||||
<DownloadingStatusCell
|
<DownloadingStatusCell
|
||||||
backend={record?.backend}
|
backend={record?.backend}
|
||||||
distributed_servers={record.distributed_servers}
|
distributed_servers={record.distributed_servers}
|
||||||
@@ -173,15 +178,17 @@ const useInstancesColumns = (options: {
|
|||||||
render: (value: string, record: ListItem) => (
|
render: (value: string, record: ListItem) => (
|
||||||
<ActionsCell
|
<ActionsCell
|
||||||
record={record}
|
record={record}
|
||||||
modelData={{
|
modelData={
|
||||||
categories: record.categories || []
|
modelList.find(
|
||||||
}}
|
(model) => model.id === record.model_id
|
||||||
|
) as ModelListItem
|
||||||
|
}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
></ActionsCell>
|
></ActionsCell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, [handleSelect, onCellClick, workerList, clusterList]);
|
}, [handleSelect, onCellClick, workerList, clusterList, modelList]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useInstancesColumns;
|
export default useInstancesColumns;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useQueryDataList } from '@gpustack/core-ui';
|
||||||
|
import { queryWorkersList } from '../apis';
|
||||||
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
export const useQueryWorkerList = (optons?: {
|
||||||
|
getLabel?: (item: ListItem) => string;
|
||||||
|
getValue?: (item: ListItem) => any;
|
||||||
|
}) => {
|
||||||
|
const { dataList, loading, fetchData, cancelRequest } = useQueryDataList<
|
||||||
|
ListItem,
|
||||||
|
Global.SearchParams
|
||||||
|
>({
|
||||||
|
key: 'workerList',
|
||||||
|
fetchList: queryWorkersList
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
dataList,
|
||||||
|
loading,
|
||||||
|
fetchData,
|
||||||
|
cancelRequest
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useQueryWorkerList;
|
||||||
Reference in New Issue
Block a user