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 aae4b443 fix: metrics error bodies, DLQ search and small UI fixes 
(#1252, #1254, #1255, #1256, #1257)
aae4b443 is described below

commit aae4b443e7ad9580a8a8af4ec78366c427cc6744
Author: lizhimins <[email protected]>
AuthorDate: Mon Aug 10 13:48:29 2026 +0800

    fix: metrics error bodies, DLQ search and small UI fixes (#1252, #1254, 
#1255, #1256, #1257)
    
    * fix(metrics): report non-JSON error bodies with their real status
    
    * fix(ui): make DLQ group search case-insensitive
    
    * fix(ui): do not report form validation failure as a save failure
    
    * fix(ui): clear topic dropdown when the instance has no topics
    
    * fix(ui): reset audit page when changing page size
    
    * test: supply real topic options in MessagePage history tests
    
    #1256 intentionally empties the topic dropdown when the backend returns no 
topics, replacing the previous fall-through to static defaults; the 
query-history tests now mock listTopics with the expected topic names instead 
of relying on the defaults.
    
    ---------
    
    Co-authored-by: yyqdbngt <[email protected]>
---
 .../AbstractPrometheusCompatibleMetricsSource.java    | 19 ++++++++++++++++---
 web/src/pages/instance/__tests__/MessagePage.test.tsx | 10 +++++++++-
 web/src/pages/instance/dlq.tsx                        |  3 ++-
 web/src/pages/instance/index.tsx                      | 10 ++++++++--
 web/src/pages/instance/message.tsx                    |  6 +++---
 web/src/pages/ops/alerts.tsx                          |  5 ++++-
 web/src/pages/ops/audit.tsx                           |  9 +++++++--
 web/src/pages/settings/index.tsx                      |  5 ++++-
 8 files changed, 53 insertions(+), 14 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java
index a0ce125d..6909c302 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/metrics/AbstractPrometheusCompatibleMetricsSource.java
@@ -100,11 +100,24 @@ public abstract class 
AbstractPrometheusCompatibleMetricsSource implements Metri
                     .headers(this::applyAuthentication)
                     .body(form)
                     .exchange((request, clientResponse) -> {
-                        JsonNode body = 
objectMapper.readTree(readResponseBody(clientResponse.getBody()));
+                        byte[] rawBody = 
readResponseBody(clientResponse.getBody());
+                        int upstreamStatus = 
responseStatus(clientResponse.getStatusCode());
                         if (clientResponse.getStatusCode().isError()) {
-                            throw responseBodyException(body, 
responseStatus(clientResponse.getStatusCode()));
+                            // Check the status before parsing so a non-JSON 
error body (e.g. a proxy
+                            // HTML/plain-text page) is not misreported as a 
connection failure.
+                            try {
+                                throw 
responseBodyException(objectMapper.readTree(rawBody), upstreamStatus);
+                            } catch (IOException nonJsonBody) {
+                                throw new PrometheusException(upstreamStatus,
+                                        backendLabel() + " query failed (HTTP 
" + upstreamStatus + ")");
+                            }
+                        }
+                        try {
+                            return objectMapper.readTree(rawBody);
+                        } catch (IOException malformedBody) {
+                            throw new 
PrometheusException(HttpStatus.BAD_GATEWAY.value(),
+                                    backendLabel() + " returned a non-JSON 
response");
                         }
-                        return body;
                     });
             return parseResponse(response);
         } catch (PrometheusException exception) {
diff --git a/web/src/pages/instance/__tests__/MessagePage.test.tsx 
b/web/src/pages/instance/__tests__/MessagePage.test.tsx
index 36dd97a3..08882926 100644
--- a/web/src/pages/instance/__tests__/MessagePage.test.tsx
+++ b/web/src/pages/instance/__tests__/MessagePage.test.tsx
@@ -36,7 +36,15 @@ vi.mock('../../../services/instanceService', () => ({
   listInstances: vi.fn().mockResolvedValue([]),
 }));
 vi.mock('../../../services/topicService', () => ({
-  listTopics: vi.fn().mockResolvedValue([]),
+  listTopics: vi
+    .fn()
+    .mockResolvedValue([
+      { name: 'order-create' },
+      { name: 'payment-callback' },
+      { name: 'user-activity-log' },
+      { name: 'notification-push' },
+      { name: 'inventory-sync' },
+    ]),
 }));
 
 import MessagePage from '../message';
diff --git a/web/src/pages/instance/dlq.tsx b/web/src/pages/instance/dlq.tsx
index dfd574fc..3010f58c 100644
--- a/web/src/pages/instance/dlq.tsx
+++ b/web/src/pages/instance/dlq.tsx
@@ -179,7 +179,8 @@ const DLQPage = () => {
     if (!search) return groups;
     return groups.filter(
       (g) =>
-        g.groupName.includes(search) || 
g.dlqTopic.toLowerCase().includes(search.toLowerCase()),
+        g.groupName.toLowerCase().includes(search.toLowerCase()) ||
+        g.dlqTopic.toLowerCase().includes(search.toLowerCase()),
     );
   }, [groups, search]);
 
diff --git a/web/src/pages/instance/index.tsx b/web/src/pages/instance/index.tsx
index 0b7eefe3..90a84a6b 100644
--- a/web/src/pages/instance/index.tsx
+++ b/web/src/pages/instance/index.tsx
@@ -215,7 +215,10 @@ const InstancePage = () => {
       setAddModalOpen(false);
       addForm.resetFields();
       setVendor(DEFAULT_VENDOR);
-    } catch {
+    } catch (error) {
+      if (error && typeof error === 'object' && 'errorFields' in error) {
+        return; // validation failure; antd already shows field-level errors
+      }
       message.error('添加实例失败,请稍后重试');
     } finally {
       setSubmitting(false);
@@ -232,7 +235,10 @@ const InstancePage = () => {
       message.success(`实例「${updated.name}」备注已更新`);
       setEditModalOpen(false);
       editForm.resetFields();
-    } catch {
+    } catch (error) {
+      if (error && typeof error === 'object' && 'errorFields' in error) {
+        return; // validation failure; antd already shows field-level errors
+      }
       message.error('更新实例失败,请稍后重试');
     } finally {
       setSubmitting(false);
diff --git a/web/src/pages/instance/message.tsx 
b/web/src/pages/instance/message.tsx
index c4a7d43a..8608f967 100644
--- a/web/src/pages/instance/message.tsx
+++ b/web/src/pages/instance/message.tsx
@@ -222,9 +222,9 @@ const MessagePage = () => {
         const scoped = selectedInstanceId
           ? nextTopics.filter((topic) => topic.instanceId === 
selectedInstanceId)
           : nextTopics;
-        if (scoped.length > 0) {
-          setTopicOptions(scoped.map((topic) => topic.name));
-        }
+        // Always update so an instance with no topics empties the dropdown 
instead of showing
+        // topics from another instance or the static defaults.
+        setTopicOptions(scoped.map((topic) => topic.name));
       })
       .catch(() => {
         // 加载失败保持静态选项可用
diff --git a/web/src/pages/ops/alerts.tsx b/web/src/pages/ops/alerts.tsx
index 7aee76bf..62b8a890 100644
--- a/web/src/pages/ops/alerts.tsx
+++ b/web/src/pages/ops/alerts.tsx
@@ -308,7 +308,10 @@ const AlertsPage = () => {
       }
       setModalVisible(false);
       form.resetFields();
-    } catch {
+    } catch (error) {
+      if (error && typeof error === 'object' && 'errorFields' in error) {
+        return; // validation failure; antd already shows field-level errors
+      }
       message.error('保存告警规则失败,请稍后重试');
     } finally {
       setSubmitting(false);
diff --git a/web/src/pages/ops/audit.tsx b/web/src/pages/ops/audit.tsx
index 808b88f6..68a82473 100644
--- a/web/src/pages/ops/audit.tsx
+++ b/web/src/pages/ops/audit.tsx
@@ -361,8 +361,13 @@ const AuditPage: React.FC = () => {
             total,
             showSizeChanger: true,
             onChange: (nextPage, nextPageSize) => {
-              setPage(nextPage);
-              setPageSize(nextPageSize);
+              if (nextPageSize !== pageSize) {
+                // A larger page size can make the current page exceed the new 
total page count.
+                setPage(1);
+                setPageSize(nextPageSize);
+              } else {
+                setPage(nextPage);
+              }
             },
           }}
         />
diff --git a/web/src/pages/settings/index.tsx b/web/src/pages/settings/index.tsx
index be988f3a..64bb0bb0 100644
--- a/web/src/pages/settings/index.tsx
+++ b/web/src/pages/settings/index.tsx
@@ -371,7 +371,10 @@ export const DataSourceTab = () => {
       message.success(editingDataSource ? '数据源已更新' : '数据源已添加');
       setModalOpen(false);
       dsForm.resetFields();
-    } catch {
+    } catch (error) {
+      if (error && typeof error === 'object' && 'errorFields' in error) {
+        return; // validation failure; antd already shows field-level errors
+      }
       message.error('保存数据源失败,请稍后重试');
     } finally {
       setSubmitting(false);

Reply via email to