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 7a7c1528 [ISSUE #1622] Guard concurrent ACL admin toggles (#1632)
7a7c1528 is described below

commit 7a7c1528aa0cd8e28ab3cfe1824654e1c88f491b
Author: 0 <[email protected]>
AuthorDate: Tue Aug 11 20:33:42 2026 +0800

    [ISSUE #1622] Guard concurrent ACL admin toggles (#1632)
---
 web/src/pages/instance/__tests__/AclPage.test.tsx | 17 ++++++++++++++++-
 web/src/pages/instance/acl.tsx                    | 16 +++++++++++++++-
 2 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/web/src/pages/instance/__tests__/AclPage.test.tsx 
b/web/src/pages/instance/__tests__/AclPage.test.tsx
index c4680512..93613964 100644
--- a/web/src/pages/instance/__tests__/AclPage.test.tsx
+++ b/web/src/pages/instance/__tests__/AclPage.test.tsx
@@ -16,7 +16,7 @@
  */
 
 import { App } from 'antd';
-import { render, screen, waitFor, within } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor, within } from 
'@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import type React from 'react';
 import { MemoryRouter } from 'react-router-dom';
@@ -213,6 +213,21 @@ describe('ACL page', () => {
     expect(payload).not.toHaveProperty('secretKey');
   });
 
+  it('ignores duplicate admin toggles while an update is pending', async () => 
{
+    vi.mocked(aclService.updateAclUser).mockImplementation(() => new 
Promise(() => {}));
+    const user = userEvent.setup();
+    renderWithProviders(<AclPage />);
+
+    await user.click(await screen.findByText('用户管理'));
+    expect(await screen.findByText('remote-admin')).toBeInTheDocument();
+    const adminSwitch = screen.getByRole('switch');
+    fireEvent.click(adminSwitch);
+    fireEvent.click(adminSwitch);
+
+    await waitFor(() => 
expect(aclService.updateAclUser).toHaveBeenCalledTimes(1));
+    expect(adminSwitch).toBeDisabled();
+  });
+
   it('does not submit masked credentials when toggling admin', async () => {
     const user = userEvent.setup();
     vi.mocked(aclService.updateAclUser).mockResolvedValue({
diff --git a/web/src/pages/instance/acl.tsx b/web/src/pages/instance/acl.tsx
index 98ebdce0..1459bf38 100644
--- a/web/src/pages/instance/acl.tsx
+++ b/web/src/pages/instance/acl.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import {
   Table,
   Card,
@@ -132,6 +132,8 @@ const AclPage = () => {
 
   // Secret key reveal
   const [revealedKeys, setRevealedKeys] = useState<Set<string>>(new Set());
+  const [adminUpdatingIds, setAdminUpdatingIds] = useState<Set<string>>(() => 
new Set());
+  const adminUpdateInFlightRef = useRef<Set<string>>(new Set());
   const [credentialsByUser, setCredentialsByUser] = useState<
     Record<string, { accessKey: string; secretKey: string }>
   >({});
@@ -359,6 +361,9 @@ const AclPage = () => {
   };
 
   const handleToggleAdmin = async (user: AclUser, checked: boolean) => {
+    if (adminUpdateInFlightRef.current.has(user.id)) return;
+    adminUpdateInFlightRef.current.add(user.id);
+    setAdminUpdatingIds((current) => new Set(current).add(user.id));
     try {
       const updated = await updateAclUser({
         id: user.id,
@@ -371,6 +376,13 @@ const AclPage = () => {
       message.success(checked ? t('acl.adminSet') : t('acl.adminRemoved'));
     } catch {
       message.error(t('common.operationFailed'));
+    } finally {
+      adminUpdateInFlightRef.current.delete(user.id);
+      setAdminUpdatingIds((current) => {
+        const next = new Set(current);
+        next.delete(user.id);
+        return next;
+      });
     }
   };
 
@@ -676,6 +688,8 @@ const AclPage = () => {
         <Switch
           checked={val}
           size="small"
+          loading={adminUpdatingIds.has(record.id)}
+          disabled={adminUpdatingIds.has(record.id)}
           onChange={(checked) => handleToggleAdmin(record, checked)}
         />
       ),

Reply via email to