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;
 }

Reply via email to