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 2fa5d787 fix(ui): batch of small UI fixes (#1169, #1170, #1171, #1172,
#1174)
2fa5d787 is described below
commit 2fa5d787de2b11a1bd369c0e52b1e0950880054a
Author: lizhimins <[email protected]>
AuthorDate: Fri Aug 7 16:26:01 2026 +0800
fix(ui): batch of small UI fixes (#1169, #1170, #1171, #1172, #1174)
* fix(ui): use the latest data source when switching metrics source
* fix(ui): surface config save errors in cluster dialog
* fix(ui): guard address search and normalize case
* fix(ui): wire up the live refresh toggle with polling
* fix(ui): tolerate partial failures in batch consumer group delete
---------
Co-authored-by: yyqdbngt <[email protected]>
---
web/src/components/MetricsExplorer.tsx | 10 ++++--
web/src/pages/cluster/clients.tsx | 2 +-
web/src/pages/cluster/index.tsx | 56 +++++++++++++++++---------------
web/src/pages/instance/consumer.tsx | 30 ++++++++++++-----
web/src/pages/studio/BrokerCluster.tsx | 11 ++++++-
web/src/pages/studio/GroupManagement.tsx | 9 +++++
web/src/services/consumerService.ts | 19 +++++++++--
7 files changed, 94 insertions(+), 43 deletions(-)
diff --git a/web/src/components/MetricsExplorer.tsx
b/web/src/components/MetricsExplorer.tsx
index 6acc99f0..1c71a791 100644
--- a/web/src/components/MetricsExplorer.tsx
+++ b/web/src/components/MetricsExplorer.tsx
@@ -252,6 +252,9 @@ const MetricsExplorer = () => {
const [dataSourceKey, setDataSourceKey] = useState('');
const [dataSourcesLoading, setDataSourcesLoading] = useState(true);
const requestId = useRef(0);
+ // Keeps the latest data source readable from the stable loadMetrics
callback so switching
+ // the source uses the new key instead of a stale closure value.
+ const dataSourceKeyRef = useRef(dataSourceKey);
const selectedProfile = useMemo(
() => profiles.find((profile) => profile.id === profileId),
@@ -277,8 +280,8 @@ const MetricsExplorer = () => {
setQueryLoading(true);
setQueryError(false);
try {
- const result = dataSourceKey
- ? await queryByDataSource({ key: dataSourceKey, query })
+ const result = dataSourceKeyRef.current
+ ? await queryByDataSource({ key: dataSourceKeyRef.current, query })
: await queryMetrics(query);
if (currentRequest === requestId.current) setData(result);
} catch {
@@ -290,7 +293,7 @@ const MetricsExplorer = () => {
if (currentRequest === requestId.current) setQueryLoading(false);
}
},
- [dataSourceKey],
+ [],
);
useEffect(() => {
@@ -343,6 +346,7 @@ const MetricsExplorer = () => {
};
const handleDataSourceChange = (nextKey: string) => {
+ dataSourceKeyRef.current = nextKey;
setDataSourceKey(nextKey);
setData(null);
void loadMetrics(selectedMetric, selectedRange);
diff --git a/web/src/pages/cluster/clients.tsx
b/web/src/pages/cluster/clients.tsx
index 8dab3d21..f4febdfb 100644
--- a/web/src/pages/cluster/clients.tsx
+++ b/web/src/pages/cluster/clients.tsx
@@ -201,7 +201,7 @@ const ClientsPage = () => {
return clusterConnections.filter(
(connection) =>
connection.clientId.toLowerCase().includes(normalizedSearch) ||
- connection.address.includes(search),
+ connection.address?.toLowerCase().includes(normalizedSearch),
);
}, [clusterConnections, search]);
diff --git a/web/src/pages/cluster/index.tsx b/web/src/pages/cluster/index.tsx
index f8558027..9468de9e 100644
--- a/web/src/pages/cluster/index.tsx
+++ b/web/src/pages/cluster/index.tsx
@@ -521,34 +521,38 @@ const ClusterPage = () => {
onOk={() => {
configForm.validateFields().then(async (values) => {
if (!selectedCluster) return;
- const { maxMessageSizeMB, ...configValues } = values;
- const nextConfig: ClusterConfig = {
- ...(selectedCluster.config ?? {}),
- ...configValues,
- maxMessageSize: maxMessageSizeMB * 1048576,
- };
- const result = await updateClusterConfig({
- id: selectedCluster.id,
- ...nextConfig,
- });
- if (result.status === 'SUCCESS') {
- await requestRefresh('operation');
- message.success(t('cluster.configUpdated'));
- setConfigModalOpen(false);
- return;
- }
+ try {
+ const { maxMessageSizeMB, ...configValues } = values;
+ const nextConfig: ClusterConfig = {
+ ...(selectedCluster.config ?? {}),
+ ...configValues,
+ maxMessageSize: maxMessageSizeMB * 1048576,
+ };
+ const result = await updateClusterConfig({
+ id: selectedCluster.id,
+ ...nextConfig,
+ });
+ if (result.status === 'SUCCESS') {
+ await requestRefresh('operation');
+ message.success(t('cluster.configUpdated'));
+ setConfigModalOpen(false);
+ return;
+ }
- const failedAddresses = result.failedBrokers
- .map((failure) => failure.address)
- .join(', ');
- if (result.status === 'PARTIAL') {
- await requestRefresh('operation');
- message.warning(
- t('cluster.configPartiallyUpdated', { brokers:
failedAddresses }),
- );
- return;
+ const failedAddresses = result.failedBrokers
+ .map((failure) => failure.address)
+ .join(', ');
+ if (result.status === 'PARTIAL') {
+ await requestRefresh('operation');
+ message.warning(
+ t('cluster.configPartiallyUpdated', { brokers:
failedAddresses }),
+ );
+ return;
+ }
+ message.error(t('cluster.configUpdateFailed', { brokers:
failedAddresses }));
+ } catch {
+ message.error(t('cluster.configUpdateFailed', { brokers: ''
}));
}
- message.error(t('cluster.configUpdateFailed', { brokers:
failedAddresses }));
});
}}
width={560}
diff --git a/web/src/pages/instance/consumer.tsx
b/web/src/pages/instance/consumer.tsx
index dce8a33e..b6c2b117 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -806,10 +806,22 @@ const ConsumerPage = () => {
cancelText: '取消',
onOk: async () => {
const names = selectedRowKeys.map(String);
- await batchDeleteConsumerGroups(names, selectedInstanceId
|| undefined);
- setGroups((prev) => prev.filter((g) =>
!names.includes(g.name)));
- message.success(`已删除 ${selectedRowKeys.length} 个 Group`);
- setSelectedRowKeys([]);
+ const { deleted, failed } = await
batchDeleteConsumerGroups(
+ names,
+ selectedInstanceId || undefined,
+ );
+ setGroups((prev) => prev.filter((g) =>
!deleted.includes(g.name)));
+ if (failed.length > 0) {
+ message.warning(
+ `已删除 ${deleted.length} 个,失败 ${failed.length}
个:${failed.join(', ')}`,
+ );
+ setSelectedRowKeys((prev) =>
+ prev.filter((key) => !deleted.includes(String(key))),
+ );
+ } else {
+ message.success(`已删除 ${deleted.length} 个 Group`);
+ setSelectedRowKeys([]);
+ }
},
});
}}
@@ -1419,11 +1431,11 @@ const ConsumerPage = () => {
if (resetGroup) {
setResetSubmitting(true);
try {
- await resetConsumerOffset({
- name: resetGroup.name,
- instanceId: selectedInstanceId || undefined,
- timestamp: resetTime.valueOf(),
- });
+ await resetConsumerOffset({
+ name: resetGroup.name,
+ instanceId: selectedInstanceId || undefined,
+ timestamp: resetTime.valueOf(),
+ });
message.success(
`${resetGroup.name} 消费位点已重置到 ${resetTime.format('YYYY-MM-DD
HH:mm:ss')}`,
);
diff --git a/web/src/pages/studio/BrokerCluster.tsx
b/web/src/pages/studio/BrokerCluster.tsx
index 3063def5..6f551bb0 100644
--- a/web/src/pages/studio/BrokerCluster.tsx
+++ b/web/src/pages/studio/BrokerCluster.tsx
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import { useCallback, useRef, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import { Table, Button, Tag, Tabs, Card, Space, Switch, Progress, Tooltip,
Spin, App } from 'antd';
import {
Plus,
@@ -158,6 +158,15 @@ const BrokerClusterPage = () => {
}
}, [message, t]);
+ // Live refresh: poll while the auto-refresh switch is on.
+ useEffect(() => {
+ if (!autoRefresh) return;
+ const timer = setInterval(() => {
+ void loadData();
+ }, 5000);
+ return () => clearInterval(timer);
+ }, [autoRefresh, loadData]);
+
const initialized = useRef<boolean | null>(null);
if (initialized.current == null) {
initialized.current = true;
diff --git a/web/src/pages/studio/GroupManagement.tsx
b/web/src/pages/studio/GroupManagement.tsx
index 7727b231..d4bc74fe 100644
--- a/web/src/pages/studio/GroupManagement.tsx
+++ b/web/src/pages/studio/GroupManagement.tsx
@@ -99,6 +99,15 @@ const GroupManagementPage = () => {
}
}, [t]);
+ // Live refresh: poll while the auto-refresh switch is on.
+ useEffect(() => {
+ if (!autoRefresh) return;
+ const timer = setInterval(() => {
+ void handleRefresh();
+ }, 5000);
+ return () => clearInterval(timer);
+ }, [autoRefresh, handleRefresh]);
+
const handleViewDetail = useCallback(
async (group: ConsumerGroup) => {
setSelectedGroup(group);
diff --git a/web/src/services/consumerService.ts
b/web/src/services/consumerService.ts
index 3f4b05e9..b48f209a 100644
--- a/web/src/services/consumerService.ts
+++ b/web/src/services/consumerService.ts
@@ -121,12 +121,25 @@ export async function resetConsumerOffset(data:
ResetConsumerOffsetRequest): Pro
return metadataApi.resetConsumerOffset(data);
}
-// Batch delete: loop through single delete calls
+export interface BatchDeleteConsumerGroupsResult {
+ deleted: string[];
+ failed: string[];
+}
+
+// Batch delete: attempt every selected group and report partial failures so a
single
+// failing group cannot silently abort the whole batch.
export async function batchDeleteConsumerGroups(
names: string[],
instanceId?: string,
-): Promise<void> {
+): Promise<BatchDeleteConsumerGroupsResult> {
+ const result: BatchDeleteConsumerGroupsResult = { deleted: [], failed: [] };
for (const name of names) {
- await deleteConsumerGroup(name, instanceId);
+ try {
+ await deleteConsumerGroup(name, instanceId);
+ result.deleted.push(name);
+ } catch {
+ result.failed.push(name);
+ }
}
+ return result;
}