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)}
/>
),