fix: prevent duplicate submit and unlock on validation failure in deploy/update modals

This commit is contained in:
jialin
2026-06-11 20:02:27 +08:00
committed by jialin
parent 6337431ed4
commit 1218c6d096
5 changed files with 86 additions and 23 deletions
@@ -59,7 +59,7 @@ type AddModalProps = {
source: SourceType;
width?: string | number;
current?: any;
onOk: (values: FormData) => void;
onOk: (values: FormData) => void | Promise<void>;
onCancel: () => void;
};
@@ -104,8 +104,14 @@ const AddModal: React.FC<AddModalProps> = (props) => {
const specListRef = useRef<any[]>([]);
const noCompatibleGPUsRef = useRef<boolean>(false);
const backendOptionsCache = useRef<any>([]);
const [loading, setLoading] = useState<boolean>(false);
const submitloadingRef = useRef<boolean>(false);
const handleSumit = () => {
if (submitloadingRef.current) {
return;
}
submitloadingRef.current = true;
form.current?.submit?.();
};
@@ -115,7 +121,11 @@ const AddModal: React.FC<AddModalProps> = (props) => {
return;
}
submitAnyway.current = true;
form.current?.submit?.();
handleSumit();
};
const onFinishFailed = () => {
submitloadingRef.current = false;
};
const generateSubmitData = (formData: FormData) => {
@@ -348,7 +358,13 @@ const AddModal: React.FC<AddModalProps> = (props) => {
..._.omit(selectSpecRef.current, ['name']),
...values
};
onOk(data);
setLoading(true);
try {
await onOk(data);
} finally {
setLoading(false);
submitloadingRef.current = false;
}
};
const handleCancel = () => {
@@ -447,6 +463,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
<ModalFooter
onCancel={handleCancel}
onOk={handleSumit}
loading={loading}
showOkBtn={!showExtraButton}
extra={
showExtraButton && (
@@ -472,6 +489,7 @@ const AddModal: React.FC<AddModalProps> = (props) => {
source={source}
action={action}
onOk={handleOk}
onFinishFailed={onFinishFailed}
ref={form}
isGGUF={isGGUF}
formKey={DeployFormKeyMap.CATALOG}
@@ -146,6 +146,8 @@ const AddModal: FC<AddModalProps> = (props) => {
const requestModelIdRef = useRef<number>(0);
const currentSelectedModel = useRef<any>({});
const flatBackendOptionsRef = useRef<any[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const submitloadingRef = useRef<boolean>(false);
const { run: fetchModelFiles } = useDeferredRequest(
() => modelFileRef.current?.fetchModelFiles?.(),
@@ -422,22 +424,33 @@ const AddModal: FC<AddModalProps> = (props) => {
};
const handleOnOk = async (allValues: FormData) => {
onOk(allValues);
setLoading(true);
await onOk(allValues);
setLoading(false);
submitloadingRef.current = false;
};
const handleSumit = () => {
if (submitloadingRef.current) {
return;
}
submitloadingRef.current = true;
form.current?.submit?.();
};
const handleSubmitAnyway = async () => {
submitAnyway.current = true;
form.current?.submit?.();
};
const handleSumit = () => {
form.current?.submit?.();
handleSumit();
};
const handleSetIsGGUF = async (flag: boolean) => {
setIsGGUF(flag);
};
const onFinishFailed = () => {
submitloadingRef.current = false;
};
const handleBackendChange = async (backend: string) => {
const data = form.current.form.getFieldsValue?.();
const res = handleBackendChangeBefore(data);
@@ -660,6 +673,7 @@ const AddModal: FC<AddModalProps> = (props) => {
<ModalFooter
onCancel={handleCancel}
onOk={handleSumit}
loading={loading}
showOkBtn={!showExtraButton}
extra={
showExtraButton && (
@@ -691,6 +705,7 @@ const AddModal: FC<AddModalProps> = (props) => {
onOk={handleOnOk}
ref={form}
isGGUF={isGGUF}
onFinishFailed={onFinishFailed}
onBackendChange={handleBackendChange}
onValuesChange={onValuesChange}
clearCacheFormValues={clearCacheFormValues}
@@ -3,7 +3,7 @@ import { PageActionType } from '@/config/types';
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import _ from 'lodash';
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import {
DeployFormKeyMap,
DO_NOT_NOTIFY_RECREATE,
@@ -29,7 +29,7 @@ type AddModalProps = {
number,
{ provider: string; state: string | number }
>[];
onOk: (values: FormData) => void;
onOk: (values: FormData) => void | Promise<void>;
onCancel: () => void;
};
@@ -60,6 +60,8 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
const formRef = useRef<any>(null);
const submitAnyway = useRef<boolean>(false);
const originFormData = useRef<any>(null);
const [loading, setLoading] = useState<boolean>(false);
const submitloadingRef = useRef<boolean>(false);
const setOriginalFormData = () => {
if (!originFormData.current) {
@@ -157,12 +159,20 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
};
const handleSumit = () => {
if (submitloadingRef.current) {
return;
}
submitloadingRef.current = true;
formRef.current?.submit();
};
const handleSubmitAnyway = async () => {
submitAnyway.current = true;
formRef.current?.submit?.();
handleSumit();
};
const onFinishFailed = () => {
submitloadingRef.current = false;
};
const handleOk = async (formdata: FormData) => {
@@ -182,7 +192,13 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}
: {})
};
onOk(submitData);
setLoading(true);
try {
await onOk(submitData);
} finally {
setLoading(false);
submitloadingRef.current = false;
}
};
const handleManulOnValuesChange = (changedValues: any, allValues: any) => {
@@ -266,6 +282,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
style={ModalFooterStyle}
onCancel={onCancel}
onOk={handleSumit}
loading={loading}
></ModalFooter>
</>
}
@@ -278,6 +295,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
realAction={realAction}
clusterList={clusterList}
onOk={handleOk}
onFinishFailed={onFinishFailed}
ref={formRef}
isGGUF={isGGUF}
onBackendChange={handleAsyncBackendChange}
@@ -76,15 +76,15 @@ const SearchResult: React.FC<SearchResultProps> = (props) => {
></IconFont>
}
description={
source === modelSourceMap.huggingface_value ? (
<div className="flex-column gap-5">
<div className="flex-column gap-5">
<span>
{intl.formatMessage({ id: 'models.search.networkerror' })}
</span>
<span>
<span>
{intl.formatMessage({ id: 'models.search.networkerror' })}
{intl.formatMessage({ id: 'models.search.hfvisit' })}
</span>
<span>
<span>
{intl.formatMessage({ id: 'models.search.hfvisit' })}
</span>
{source === modelSourceMap.huggingface_value ? (
<Button
type="link"
size="small"
@@ -93,9 +93,18 @@ const SearchResult: React.FC<SearchResultProps> = (props) => {
>
Hugging Face
</Button>
</span>
</div>
) : null
) : (
<Button
type="link"
size="small"
href="https://modelscope.cn/"
target="_blank"
>
ModelScope
</Button>
)}
</span>
</div>
}
/>
);
+3
View File
@@ -66,6 +66,7 @@ interface DataFormProps {
onOk: (values: FormData) => void;
onBackendChange?: (value: string) => void;
onClusterChange?: (value: number) => void;
onFinishFailed?: (errorInfo: any) => void;
}
const TABKeysMap = {
@@ -91,6 +92,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
onSourceChange,
onValuesChange,
onClusterChange,
onFinishFailed,
onOk
} = props;
const { getScrollElementScrollableHeight } = useWrapperContext();
@@ -285,6 +287,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
const handleOnFinishFailed = (errorInfo: any) => {
setSubmitAttempted(true);
onFinishFailed?.(errorInfo);
console.log('Failed:', errorInfo);
const { errorFields } = errorInfo;
if (errorFields && errorFields.length > 0) {