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