This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new b080ee60 [ISSUE #1616] Prevent duplicate instance submissions (#1626)
b080ee60 is described below

commit b080ee6073b45140c99b35bbe794f90ab6e60917
Author: 0 <[email protected]>
AuthorDate: Tue Aug 11 20:31:53 2026 +0800

    [ISSUE #1616] Prevent duplicate instance submissions (#1626)
---
 .../pages/instance/__tests__/InstancePage.test.tsx | 24 ++++++++++++++++++++++
 web/src/pages/instance/index.tsx                   |  8 +++++++-
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/web/src/pages/instance/__tests__/InstancePage.test.tsx 
b/web/src/pages/instance/__tests__/InstancePage.test.tsx
index 2fd2442b..866d436c 100644
--- a/web/src/pages/instance/__tests__/InstancePage.test.tsx
+++ b/web/src/pages/instance/__tests__/InstancePage.test.tsx
@@ -186,6 +186,30 @@ describe('InstancePage', () => {
     expect(screen.getByText('latest-instance')).toBeInTheDocument();
   });
 
+  it('ignores duplicate create submissions while the first request is 
pending', async () => {
+    const user = userEvent.setup();
+    vi.mocked(instanceService.createInstance).mockImplementation(() => new 
Promise(() => {}));
+    renderPage();
+
+    expect(await screen.findByText('production-proxy')).toBeInTheDocument();
+    await user.click(screen.getByRole('button', { name: /添加实例/ }));
+    const dialog = await screen.findByRole('dialog');
+    await user.type(within(dialog).getByLabelText('实例名称'), 'new-proxy');
+    const createTypeSelect = within(dialog).getByRole('combobox');
+    fireEvent.mouseDown(createTypeSelect.parentElement!);
+    const proxyOptions = await screen.findAllByText('Proxy 模式', {
+      selector: '.ant-select-item-option-content',
+    });
+    await user.click(proxyOptions[proxyOptions.length - 1]);
+    await user.type(within(dialog).getByLabelText('接入地址'), 'proxy-new:8080');
+    const connect = within(dialog).getByRole('button', { name: /连\s*接/ });
+
+    fireEvent.click(connect);
+    fireEvent.click(connect);
+
+    await waitFor(() => 
expect(instanceService.createInstance).toHaveBeenCalledTimes(1));
+  });
+
   it('reloads the current filters after creating an instance', async () => {
     const user = userEvent.setup();
     
vi.mocked(instanceService.createInstance).mockResolvedValue(instance('created', 
'new-proxy'));
diff --git a/web/src/pages/instance/index.tsx b/web/src/pages/instance/index.tsx
index 4eb346cd..0f507e28 100644
--- a/web/src/pages/instance/index.tsx
+++ b/web/src/pages/instance/index.tsx
@@ -98,6 +98,7 @@ const InstancePage = () => {
   const [editForm] = Form.useForm();
   const [submitting, setSubmitting] = useState(false);
   const requestIdRef = useRef(0);
+  const mutationInFlightRef = useRef(false);
 
   useEffect(() => {
     const timer = window.setTimeout(() => setDebouncedSearch(search.trim()), 
300);
@@ -276,6 +277,8 @@ const InstancePage = () => {
   };
 
   const handleCreate = async () => {
+    if (mutationInFlightRef.current) return;
+    mutationInFlightRef.current = true;
     try {
       const values = await addForm.validateFields();
       setSubmitting(true);
@@ -301,12 +304,14 @@ const InstancePage = () => {
       }
       message.error('添加实例失败,请稍后重试');
     } finally {
+      mutationInFlightRef.current = false;
       setSubmitting(false);
     }
   };
 
   const handleUpdate = async () => {
-    if (!editingInstance) return;
+    if (!editingInstance || mutationInFlightRef.current) return;
+    mutationInFlightRef.current = true;
     try {
       const values = await editForm.validateFields();
       setSubmitting(true);
@@ -325,6 +330,7 @@ const InstancePage = () => {
       }
       message.error('更新实例失败,请稍后重试');
     } finally {
+      mutationInFlightRef.current = false;
       setSubmitting(false);
     }
   };

Reply via email to