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