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 0b888d7d fix: prevent white screen when cluster has no proxy (#996)
0b888d7d is described below

commit 0b888d7d025b10cd6fe1feaa811a071587d7169f
Author: keranbingaa <[email protected]>
AuthorDate: Wed Aug 5 17:41:47 2026 +0800

    fix: prevent white screen when cluster has no proxy (#996)
    
    RocketMQClusterProvider left proxies/tpsHistory as null in the returned
    ClusterVO, which crashed the cluster page render (c.proxies.length) because
    the app has no error boundary. Add safe defaults in the provider and null
    guards in the cluster page for proxies/config/brokers/nameServers.
    
    Extend provider tests to cover the safe defaults.
    
    Co-authored-by: Developer <[email protected]>
---
 .../studio/rocketmq/RocketMQClusterProvider.java   | 37 ++++++++++++---------
 .../rocketmq/RocketMQClusterProviderTest.java      | 38 ++++++++++++++++++++++
 web/src/pages/cluster/index.tsx                    | 22 ++++++-------
 3 files changed, 71 insertions(+), 26 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProvider.java
index 9b291bdd..7d5c7efd 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProvider.java
@@ -83,13 +83,7 @@ public class RocketMQClusterProvider implements 
ClusterProvider {
                 List<BrokerVO> brokers = buildBrokerList(brokerNames, 
brokerAddrTable);
                 List<NameServerVO> nameServers = buildNameServerList();
 
-                ClusterVO cluster = ClusterVO.builder()
-                        .name(clusterName)
-                        .status(ClusterStatus.healthy)
-                        .brokers(brokers)
-                        .nameServers(nameServers)
-                        .build();
-                cluster.setId(clusterName);
+                ClusterVO cluster = buildClusterVO(clusterName, brokers, 
nameServers);
                 clusters.add(cluster);
             }
             return clusters;
@@ -123,20 +117,33 @@ public class RocketMQClusterProvider implements 
ClusterProvider {
             List<BrokerVO> brokers = buildBrokerList(brokerNames, 
brokerAddrTable);
             List<NameServerVO> nameServers = buildNameServerList();
 
-            ClusterVO cluster = ClusterVO.builder()
-                    .name(clusterId)
-                    .status(ClusterStatus.healthy)
-                    .brokers(brokers)
-                    .nameServers(nameServers)
-                    .build();
-            cluster.setId(clusterId);
-            return cluster;
+            return buildClusterVO(clusterId, brokers, nameServers);
         } catch (Exception e) {
             log.warn("Failed to refresh cluster detail for {}: {}", clusterId, 
e.getMessage());
             return null;
         }
     }
 
+    /**
+     * Build a cluster view with safe defaults so downstream consumers (web 
UI, dashboard)
+     * never receive null collections for fields like proxies or tpsHistory.
+     */
+    private ClusterVO buildClusterVO(String clusterName, List<BrokerVO> 
brokers,
+                                     List<NameServerVO> nameServers) {
+        ClusterVO cluster = ClusterVO.builder()
+                .name(clusterName)
+                .status(ClusterStatus.healthy)
+                .brokers(brokers != null ? brokers : Collections.emptyList())
+                .proxies(Collections.emptyList())
+                .nameServers(nameServers != null ? nameServers : 
Collections.emptyList())
+                .tpsHistory(Collections.emptyList())
+                .topicCount(0)
+                .groupCount(0)
+                .build();
+        cluster.setId(clusterName);
+        return cluster;
+    }
+
     private List<BrokerVO> buildBrokerList(Set<String> brokerNames,
                                            Map<String, BrokerData> 
brokerAddrTable) {
         List<BrokerVO> brokers = new ArrayList<>();
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProviderTest.java
index 7a686c6b..4d432ce0 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/rocketmq/RocketMQClusterProviderTest.java
@@ -51,6 +51,44 @@ class RocketMQClusterProviderTest {
         
assertThat(clusters.get(0).getBrokers().get(0).getTpsOut()).isEqualTo(34);
     }
 
+    @Test
+    void discoverClustersShouldPopulateSafeDefaults() throws Exception {
+        DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+        RocketMQProperties properties = new RocketMQProperties();
+        properties.setNamesrvAddr("10.0.0.1:9876");
+        RocketMQClusterProvider provider = new 
RocketMQClusterProvider(adminExt, properties);
+
+        when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
+
+        List<ClusterVO> clusters = provider.discoverClusters();
+
+        assertThat(clusters).hasSize(1);
+        ClusterVO cluster = clusters.get(0);
+        assertThat(cluster.getName()).isEqualTo("DefaultCluster");
+        // Real cluster has no proxies configured - must never be null for the 
web UI
+        assertThat(cluster.getProxies()).isNotNull().isEmpty();
+        assertThat(cluster.getTpsHistory()).isNotNull().isEmpty();
+        assertThat(cluster.getNameServers()).hasSize(1);
+        
assertThat(cluster.getNameServers().get(0).getAddr()).isEqualTo("10.0.0.1:9876");
+    }
+
+    @Test
+    void refreshClusterDetailShouldPopulateSafeDefaults() throws Exception {
+        DefaultMQAdminExt adminExt = mock(DefaultMQAdminExt.class);
+        RocketMQProperties properties = new RocketMQProperties();
+        properties.setNamesrvAddr("10.0.0.1:9876");
+        RocketMQClusterProvider provider = new 
RocketMQClusterProvider(adminExt, properties);
+
+        when(adminExt.examineBrokerClusterInfo()).thenReturn(clusterInfo());
+
+        ClusterVO cluster = provider.refreshClusterDetail("DefaultCluster");
+
+        assertThat(cluster).isNotNull();
+        assertThat(cluster.getId()).isEqualTo("DefaultCluster");
+        assertThat(cluster.getProxies()).isNotNull().isEmpty();
+        assertThat(cluster.getTpsHistory()).isNotNull().isEmpty();
+    }
+
     private ClusterInfo clusterInfo() {
         ClusterInfo clusterInfo = new ClusterInfo();
         HashMap<Long, String> addrs = new HashMap<>();
diff --git a/web/src/pages/cluster/index.tsx b/web/src/pages/cluster/index.tsx
index 031e5bff..f285d2ee 100644
--- a/web/src/pages/cluster/index.tsx
+++ b/web/src/pages/cluster/index.tsx
@@ -182,7 +182,7 @@ const ClusterPage = () => {
             setSelectedProxy((current) => {
               if (!current) return null;
               const cluster = nextClusters.find((item) => item.id === 
current.clusterId);
-              const proxy = cluster?.proxies.find((item) => item.addr === 
current.addr);
+              const proxy = cluster?.proxies?.find((item) => item.addr === 
current.addr);
               if (!cluster || !proxy) return null;
               return {
                 ...proxy,
@@ -278,13 +278,13 @@ const ClusterPage = () => {
 
   // Broker config handler
   const handleConfigOpen = (cluster: ClusterInfo) => {
-    const cfg: ClusterConfig = cluster.config;
+    const cfg: ClusterConfig = cluster.config ?? ({} as ClusterConfig);
     setSelectedCluster(cluster);
     configForm.setFieldsValue({
       flushDiskType: cfg.flushDiskType ?? 'ASYNC_FLUSH',
       autoCreateTopicEnable: cfg.autoCreateTopicEnable ?? false,
       autoCreateSubscriptionGroup: cfg.autoCreateSubscriptionGroup ?? false,
-      maxMessageSizeMB: Math.round(cfg.maxMessageSize / 1048576),
+      maxMessageSizeMB: Math.round((cfg.maxMessageSize ?? 4194304) / 1048576),
       fileReservedTime: cfg.fileReservedTime ?? 72,
       writeQueueNums: cfg.writeQueueNums ?? 8,
       readQueueNums: cfg.readQueueNums ?? 8,
@@ -337,7 +337,7 @@ const ClusterPage = () => {
     const brokerSearchText = searchText(brokerSearch);
 
     const allBrokers: BrokerWithCluster[] = clusters.flatMap((c) =>
-      c.brokers
+      (c.brokers ?? [])
         .filter((b) => {
           const matchSearch =
             !brokerSearchText ||
@@ -522,7 +522,7 @@ const ClusterPage = () => {
                 if (!selectedCluster) return;
                 const { maxMessageSizeMB, ...configValues } = values;
                 const nextConfig: ClusterConfig = {
-                  ...selectedCluster.config,
+                  ...(selectedCluster.config ?? {}),
                   ...configValues,
                   maxMessageSize: maxMessageSizeMB * 1048576,
                 };
@@ -590,7 +590,7 @@ const ClusterPage = () => {
     const nsSearchText = searchText(nsSearch);
     const filteredClusters = clusters
       .map((c) => {
-        const nameServers = c.nameServers.filter((ns) => {
+        const nameServers = (c.nameServers ?? []).filter((ns) => {
           const matchSearch = !nsSearchText || 
searchText(ns.addr).includes(nsSearchText);
           return matchSearch;
         });
@@ -764,9 +764,9 @@ const ClusterPage = () => {
     const proxySearchText = searchText(proxySearch);
 
     const allProxies: ProxyRow[] = clusters
-      .filter((c) => c.proxies.length > 0)
+      .filter((c) => (c.proxies?.length ?? 0) > 0)
       .flatMap((c) =>
-        c.proxies
+        (c.proxies ?? [])
           .filter((p) => {
             const matchSearch = !proxySearchText || 
searchText(p.addr).includes(proxySearchText);
             return matchSearch;
@@ -919,9 +919,9 @@ const ClusterPage = () => {
 
   // ─── Render 
─────────────────────────────────────────────────────────────────
 
-  const totalBrokers = clusters.reduce((s, c) => s + c.brokers.length, 0);
-  const totalNameServers = clusters.reduce((s, c) => s + c.nameServers.length, 
0);
-  const totalProxies = clusters.reduce((s, c) => s + c.proxies.length, 0);
+  const totalBrokers = clusters.reduce((s, c) => s + (c.brokers?.length ?? 0), 
0);
+  const totalNameServers = clusters.reduce((s, c) => s + 
(c.nameServers?.length ?? 0), 0);
+  const totalProxies = clusters.reduce((s, c) => s + (c.proxies?.length ?? 0), 
0);
 
   return (
     <div style={{ padding: 24 }}>

Reply via email to