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
commit c20f6ab64a34d51a3b5c04d31e71f3812cf974f1 Author: yyqdbngt <[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: lizhimins <[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);
