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 fbdcf867 fix: encode topic and consumer group path segments (#611)
fbdcf867 is described below
commit fbdcf867ad0f77f9505f7c17cc6c9cbe5333ca32
Author: yx9o <[email protected]>
AuthorDate: Tue Jul 28 20:52:18 2026 +0800
fix: encode topic and consumer group path segments (#611)
---
web/src/api/consumerGroups.test.ts | 19 ++++++++++++++++++-
web/src/api/metadata.test.ts | 18 +++++++++++++++++-
web/src/api/metadata.ts | 20 +++++++++++++++-----
3 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/web/src/api/consumerGroups.test.ts
b/web/src/api/consumerGroups.test.ts
index 7cf943d9..c997a4d0 100644
--- a/web/src/api/consumerGroups.test.ts
+++ b/web/src/api/consumerGroups.test.ts
@@ -18,7 +18,13 @@
import MockAdapter from 'axios-mock-adapter';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import client from './client';
-import { getConsumerGroup, listConsumerGroups, resetConsumerOffset } from
'./metadata';
+import {
+ getConsumerGroup,
+ getConsumerProgress,
+ getConsumerSubscriptions,
+ listConsumerGroups,
+ resetConsumerOffset,
+} from './metadata';
const mock = new MockAdapter(client);
const group = {
@@ -59,6 +65,17 @@ describe('consumer groups API contract', () => {
await expect(listConsumerGroups(params)).resolves.toEqual([group]);
});
+ it('encodes consumer group names used in path segments', async () => {
+ const groupName = '%RETRY%cg-order';
+ mock.onGet('/groups/%25RETRY%25cg-order').reply(200, { code: 200, data:
group });
+ mock.onGet('/groups/%25RETRY%25cg-order/progress').reply(200, { code: 200,
data: [] });
+ mock.onGet('/groups/%25RETRY%25cg-order/subscriptions').reply(200, { code:
200, data: [] });
+
+ await expect(getConsumerGroup(groupName)).resolves.toEqual(group);
+ await expect(getConsumerProgress(groupName)).resolves.toEqual([]);
+ await expect(getConsumerSubscriptions(groupName)).resolves.toEqual([]);
+ });
+
it('unwraps detail records and sends numeric reset timestamps', async () => {
const reset = { name: group.name, topic: 'orders', timestamp:
1784246400000 };
mock.onGet('/groups/orders').reply(200, { code: 200, data: group });
diff --git a/web/src/api/metadata.test.ts b/web/src/api/metadata.test.ts
index 4b18492d..3a57c1bc 100644
--- a/web/src/api/metadata.test.ts
+++ b/web/src/api/metadata.test.ts
@@ -18,7 +18,14 @@
import MockAdapter from 'axios-mock-adapter';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import client from './client';
-import { createTopic, deleteTopic, listTopics, sendTopicMessage } from
'./metadata';
+import {
+ createTopic,
+ deleteTopic,
+ getTopicConsumers,
+ getTopicRoutes,
+ listTopics,
+ sendTopicMessage,
+} from './metadata';
const mock = new MockAdapter(client);
@@ -43,6 +50,15 @@ describe('topic metadata API', () => {
await expect(listTopics(params)).resolves.toEqual([]);
});
+ it('encodes topic names used in path segments', async () => {
+ const topicName = '%DLQ%cg-order';
+ mock.onGet('/topics/%25DLQ%25cg-order/routes').reply(200, { code: 200,
data: [] });
+ mock.onGet('/topics/%25DLQ%25cg-order/consumers').reply(200, { code: 200,
data: [] });
+
+ await expect(getTopicRoutes(topicName)).resolves.toEqual([]);
+ await expect(getTopicConsumers(topicName)).resolves.toEqual([]);
+ });
+
it('persists topic creation, deletion, and sending through API endpoints',
async () => {
const topic = {
name: 'orders',
diff --git a/web/src/api/metadata.ts b/web/src/api/metadata.ts
index abb45c91..bb85ec7b 100644
--- a/web/src/api/metadata.ts
+++ b/web/src/api/metadata.ts
@@ -124,12 +124,16 @@ export async function deleteTopic(name: string) {
}
export async function getTopicRoutes(name: string) {
- const res = await client.get<{ data: BrokerRoute[]
}>(`/topics/${name}/routes`);
+ const res = await client.get<{ data: BrokerRoute[] }>(
+ `/topics/${encodeURIComponent(name)}/routes`,
+ );
return res.data.data;
}
export async function getTopicConsumers(name: string) {
- const res = await client.get<{ data: ConsumerGroupInfo[]
}>(`/topics/${name}/consumers`);
+ const res = await client.get<{ data: ConsumerGroupInfo[] }>(
+ `/topics/${encodeURIComponent(name)}/consumers`,
+ );
return res.data.data;
}
@@ -159,17 +163,23 @@ export async function listConsumerGroups(params?:
ConsumerGroupQuery) {
}
export async function getConsumerGroup(name: string) {
- const res = await client.get<{ data: ConsumerGroupDetail
}>(`/groups/${name}`);
+ const res = await client.get<{ data: ConsumerGroupDetail }>(
+ `/groups/${encodeURIComponent(name)}`,
+ );
return res.data.data;
}
export async function getConsumerProgress(name: string) {
- const res = await client.get<{ data: QueueProgress[]
}>(`/groups/${name}/progress`);
+ const res = await client.get<{ data: QueueProgress[] }>(
+ `/groups/${encodeURIComponent(name)}/progress`,
+ );
return res.data.data;
}
export async function getConsumerSubscriptions(name: string) {
- const res = await client.get<{ data: SubscriptionEntry[]
}>(`/groups/${name}/subscriptions`);
+ const res = await client.get<{ data: SubscriptionEntry[] }>(
+ `/groups/${encodeURIComponent(name)}/subscriptions`,
+ );
return res.data.data;
}