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 3398fcce fix: route consumer group operations by selected instance 
(#1130)
3398fcce is described below

commit 3398fccecc6c003b70a4e2fa4d64ee48060169a6
Author: aias00 <[email protected]>
AuthorDate: Fri Aug 7 00:13:05 2026 -0700

    fix: route consumer group operations by selected instance (#1130)
    
    * fix: scope group diagnostics to selected instance
    
    * fix: scope offset resets to selected instance
    
    * fix: propagate instance context for group diagnostics
    
    * fix: route consumer group creation by instance
    
    * fix: route consumer group deletion by instance
    
    * fix: route consumer group detail by instance
---
 .../instance/group/ResetConsumerOffsetDTO.java     |   5 +-
 .../studio/instance/topic/MetadataService.java     |   2 +-
 .../studio/instance/topic/NameSrvAdminClient.java  |   6 +-
 .../studio/provider/apache/AdminClient.java        |   6 +-
 .../provider/apache/ApacheInstanceProvider.java    |   8 +-
 .../studio/provider/apache/MetadataProvider.java   |   4 +-
 .../provider/apache/RocketMQAdminClientImpl.java   | 241 ++++++++++++---------
 .../provider/apache/RocketMQMetadataProvider.java  | 114 +++++-----
 .../group/ConsumerGroupControllerTest.java         |  25 ++-
 .../instance/topic/NameSrvAdminClientTest.java     |   6 +-
 .../apache/RocketMQAdminClientImplTest.java        |  94 +++++++-
 .../apache/RocketMQMetadataProviderTest.java       |  16 ++
 web/src/api/consumerGroups.test.ts                 |  17 +-
 web/src/api/metadata.ts                            |   2 +-
 .../pages/instance/__tests__/ConsumerPage.test.tsx |  47 ++++
 web/src/pages/instance/consumer.tsx                |  10 +-
 16 files changed, 412 insertions(+), 191 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/group/ResetConsumerOffsetDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ResetConsumerOffsetDTO.java
index 64111393..80ea5f75 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/group/ResetConsumerOffsetDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ResetConsumerOffsetDTO.java
@@ -29,6 +29,9 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class ResetConsumerOffsetDTO {
+    @NotBlank(message = "instanceId is required")
+    private String instanceId;
+
     @NotBlank(message = "name is required")
     private String name;
 
@@ -37,6 +40,4 @@ public class ResetConsumerOffsetDTO {
     private Long timestamp;
 
     private String topic;
-
-    private String instanceId;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
index fa7b1eae..d67ace2a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/MetadataService.java
@@ -132,7 +132,7 @@ public class MetadataService {
         if (resolve(instanceId).vendor() != InstanceVendor.APACHE) {
             throw new BusinessException(501, "Consumer group detail is not 
supported for cloud instances");
         }
-        return adminClient.getConsumerGroup(name);
+        return adminClient.getConsumerGroup(instanceId, name);
     }
 
 
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java
index f91765b2..c4de8986 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClient.java
@@ -34,7 +34,7 @@ public class NameSrvAdminClient implements AdminClient {
     }
 
     @Override
-    public ConsumerGroupVO getConsumerGroup(String name) {
+    public ConsumerGroupVO getConsumerGroup(String instanceId, String name) {
         throw consumerGroupAdminUnavailable();
     }
 
@@ -64,12 +64,12 @@ public class NameSrvAdminClient implements AdminClient {
     }
 
     @Override
-    public void deleteConsumerGroup(String name) {
+    public void deleteConsumerGroup(String instanceId, String name) {
         throw consumerGroupAdminUnavailable();
     }
 
     @Override
-    public void resetOffset(String name, long timestamp, String topic) {
+    public void resetOffset(String instanceId, String name, long timestamp, 
String topic) {
         throw consumerGroupAdminUnavailable();
     }
 
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/AdminClient.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/AdminClient.java
index 90e958b9..bc394080 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/AdminClient.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/AdminClient.java
@@ -24,12 +24,12 @@ import 
org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 
 public interface AdminClient {
     TopicVO getTopic(String name);
-    ConsumerGroupVO getConsumerGroup(String name);
+    ConsumerGroupVO getConsumerGroup(String instanceId, String name);
     TopicVO createTopic(TopicVO topic);
     TopicVO updateTopic(TopicVO topic);
     void deleteTopic(String name);
     SendMessageVO sendMessage(SendMessageDTO request);
     ConsumerGroupVO createConsumerGroup(ConsumerGroupVO group);
-    void deleteConsumerGroup(String name);
-    void resetOffset(String name, long timestamp, String topic);
+    void deleteConsumerGroup(String instanceId, String name);
+    void resetOffset(String instanceId, String name, long timestamp, String 
topic);
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
index 4915303a..665ac771 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/ApacheInstanceProvider.java
@@ -102,22 +102,22 @@ public class ApacheInstanceProvider implements 
InstanceProvider {
 
     @Override
     public void deleteConsumerGroup(String instanceId, String groupName) {
-        adminClient.deleteConsumerGroup(groupName);
+        adminClient.deleteConsumerGroup(instanceId, groupName);
     }
 
     @Override
     public List<QueueProgressVO> getGroupProgress(String instanceId, String 
groupName) {
-        return metadataProvider.getGroupProgress(groupName);
+        return metadataProvider.getGroupProgress(instanceId, groupName);
     }
 
     @Override
     public List<SubscriptionEntryVO> getGroupSubscriptions(String instanceId, 
String groupName) {
-        return metadataProvider.getGroupSubscriptions(groupName);
+        return metadataProvider.getGroupSubscriptions(instanceId, groupName);
     }
 
     @Override
     public void resetOffset(String instanceId, String groupName, long 
timestamp, String topic) {
-        adminClient.resetOffset(groupName, timestamp, topic);
+        adminClient.resetOffset(instanceId, groupName, timestamp, topic);
     }
 
     @Override
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
index 173e7c8a..9acfe40e 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/MetadataProvider.java
@@ -30,6 +30,6 @@ public interface MetadataProvider {
     List<ConsumerGroupVO> listConsumerGroups(String clusterId, String search);
     List<BrokerRouteVO> getTopicRoutes(String instanceId, String name);
     List<TopicConsumerVO> getTopicConsumers(String instanceId, String name);
-    List<QueueProgressVO> getGroupProgress(String name);
-    List<SubscriptionEntryVO> getGroupSubscriptions(String name);
+    List<QueueProgressVO> getGroupProgress(String instanceId, String name);
+    List<SubscriptionEntryVO> getGroupSubscriptions(String instanceId, String 
name);
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
index 10da98ce..f6fc162c 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImpl.java
@@ -26,6 +26,7 @@ import org.apache.rocketmq.remoting.protocol.ResponseCode;
 import org.apache.rocketmq.remoting.protocol.route.BrokerData;
 import 
org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
 import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
+import org.apache.rocketmq.studio.cluster.broker.RuntimeAdminClientResolver;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.common.domain.enums.TopicPerm;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
@@ -69,6 +70,7 @@ public class RocketMQAdminClientImpl implements AdminClient {
     private final RmqTopicMapper topicMapper;
     private final RmqGroupMapper groupMapper;
     private final AuditService auditService;
+    private final RuntimeAdminClientResolver runtimeAdminClientResolver;
 
     @Override
     public TopicVO getTopic(String name) {
@@ -94,32 +96,37 @@ public class RocketMQAdminClientImpl implements AdminClient 
{
     }
 
     @Override
-    public ConsumerGroupVO getConsumerGroup(String name) {
-        return adminFactory.execute(namesrvAddr(), null, admin -> {
-            ConsumerGroupVO vo = new ConsumerGroupVO();
-            vo.setId(name);
-            vo.setName(name);
-            try {
-                var conn = admin.examineConsumerConnectionInfo(name);
-                if (conn != null) {
-                    if (conn.getConnectionSet() != null) {
-                        vo.setOnlineInstances(conn.getConnectionSet().size());
-                    }
-                    if (conn.getSubscriptionTable() != null) {
-                        vo.setSubscribedTopics(new 
ArrayList<>(conn.getSubscriptionTable().keySet()));
-                    }
+    public ConsumerGroupVO getConsumerGroup(String instanceId, String name) {
+        if (StringUtils.hasText(instanceId)) {
+            return runtimeAdminClientResolver.execute(instanceId, admin -> 
getConsumerGroup(admin, name));
+        }
+        return adminFactory.execute(namesrvAddr(), null, admin -> 
getConsumerGroup(admin, name));
+    }
+
+    private ConsumerGroupVO getConsumerGroup(MQAdminExt admin, String name) {
+        ConsumerGroupVO vo = new ConsumerGroupVO();
+        vo.setId(name);
+        vo.setName(name);
+        try {
+            var conn = admin.examineConsumerConnectionInfo(name);
+            if (conn != null) {
+                if (conn.getConnectionSet() != null) {
+                    vo.setOnlineInstances(conn.getConnectionSet().size());
                 }
-            } catch (MQClientException exception) {
-                if (exception.getResponseCode() == 
ResponseCode.CONSUMER_NOT_ONLINE) {
-                    log.debug("Consumer group {} is offline", name);
-                    return vo;
+                if (conn.getSubscriptionTable() != null) {
+                    vo.setSubscribedTopics(new 
ArrayList<>(conn.getSubscriptionTable().keySet()));
                 }
-                throw new BusinessException(502, "Failed to get consumer 
group: " + exception.getMessage());
-            } catch (Exception exception) {
-                throw new BusinessException(502, "Failed to get consumer 
group: " + exception.getMessage());
             }
-            return vo;
-        });
+        } catch (MQClientException exception) {
+            if (exception.getResponseCode() == 
ResponseCode.CONSUMER_NOT_ONLINE) {
+                log.debug("Consumer group {} is offline", name);
+                return vo;
+            }
+            throw new BusinessException(502, "Failed to get consumer group: " 
+ exception.getMessage());
+        } catch (Exception exception) {
+            throw new BusinessException(502, "Failed to get consumer group: " 
+ exception.getMessage());
+        }
+        return vo;
     }
 
     @Override
@@ -355,106 +362,130 @@ public class RocketMQAdminClientImpl implements 
AdminClient {
 
     @Override
     public ConsumerGroupVO createConsumerGroup(ConsumerGroupVO group) {
+        if (group != null && StringUtils.hasText(group.getInstanceId())) {
+            return runtimeAdminClientResolver.execute(group.getInstanceId(),
+                    admin -> createConsumerGroup(admin, group));
+        }
+        return adminFactory.execute(namesrvAddr(), null, admin -> 
createConsumerGroup(admin, group));
+    }
+
+    private ConsumerGroupVO createConsumerGroup(MQAdminExt admin, 
ConsumerGroupVO group) {
         String groupName = group.getName();
 
-        return adminFactory.execute(namesrvAddr(), null, admin -> {
-            try {
-                Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
-                if (brokerAddrs.isEmpty()) {
-                    throw new BusinessException(500, "No broker available to 
create consumer group");
-                }
+        try {
+            Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
+            if (brokerAddrs.isEmpty()) {
+                throw new BusinessException(500, "No broker available to 
create consumer group");
+            }
 
-                SubscriptionGroupConfig config = new SubscriptionGroupConfig();
-                config.setGroupName(groupName);
-                config.setConsumeEnable(true);
-                config.setConsumeBroadcastEnable(true);
-                config.setRetryQueueNums(1);
-                config.setRetryMaxTimes(group.getRetryMaxTimes() > 0 ? 
group.getRetryMaxTimes() : 16);
+            SubscriptionGroupConfig config = new SubscriptionGroupConfig();
+            config.setGroupName(groupName);
+            config.setConsumeEnable(true);
+            config.setConsumeBroadcastEnable(true);
+            config.setRetryQueueNums(1);
+            config.setRetryMaxTimes(group.getRetryMaxTimes() > 0 ? 
group.getRetryMaxTimes() : 16);
 
-                for (String addr : brokerAddrs) {
-                    admin.createAndUpdateSubscriptionGroupConfig(addr, config);
-                }
+            for (String addr : brokerAddrs) {
+                admin.createAndUpdateSubscriptionGroupConfig(addr, config);
+            }
 
-                // Persist to DB, upserting so re-creating an existing group 
does not violate the
-                // unique (cluster_id, name) key.
-                String groupClusterName = getClusterName(admin);
-                RmqGroup entity = groupMapper.selectOne(new 
LambdaQueryWrapper<RmqGroup>()
-                        .eq(RmqGroup::getClusterId, groupClusterName)
-                        .eq(RmqGroup::getName, groupName));
-                boolean isNewGroup = entity == null;
-                if (isNewGroup) {
-                    entity = new RmqGroup();
-                    entity.setName(groupName);
-                    entity.setClusterId(groupClusterName);
-                    entity.setCreatedAt(LocalDateTime.now());
-                }
-                if (StringUtils.hasText(group.getInstanceId())) {
-                    entity.setInstanceId(group.getInstanceId());
-                }
-                entity.setConsumeType(group.getConsumeType() != null ? 
group.getConsumeType().name() : "CLUSTERING");
-                entity.setMessageModel(group.getSubscriptionMode() != null ? 
group.getSubscriptionMode().name() : "Push");
-                entity.setMaxRetry(config.getRetryMaxTimes());
-                entity.setStatus("ACTIVE");
-                entity.setUpdatedAt(LocalDateTime.now());
-                if (isNewGroup) {
-                    groupMapper.insert(entity);
-                } else {
-                    groupMapper.updateById(entity);
-                }
+            // Persist to DB, upserting so re-creating an existing group does 
not violate the
+            // unique (cluster_id, name) key.
+            String groupClusterName = getClusterName(admin);
+            RmqGroup entity = groupMapper.selectOne(new 
LambdaQueryWrapper<RmqGroup>()
+                    .eq(RmqGroup::getClusterId, groupClusterName)
+                    .eq(RmqGroup::getName, groupName));
+            boolean isNewGroup = entity == null;
+            if (isNewGroup) {
+                entity = new RmqGroup();
+                entity.setName(groupName);
+                entity.setClusterId(groupClusterName);
+                entity.setCreatedAt(LocalDateTime.now());
+            }
+            if (StringUtils.hasText(group.getInstanceId())) {
+                entity.setInstanceId(group.getInstanceId());
+            }
+            entity.setConsumeType(group.getConsumeType() != null ? 
group.getConsumeType().name() : "CLUSTERING");
+            entity.setMessageModel(group.getSubscriptionMode() != null ? 
group.getSubscriptionMode().name() : "Push");
+            entity.setMaxRetry(config.getRetryMaxTimes());
+            entity.setStatus("ACTIVE");
+            entity.setUpdatedAt(LocalDateTime.now());
+            if (isNewGroup) {
+                groupMapper.insert(entity);
+            } else {
+                groupMapper.updateById(entity);
+            }
 
-                auditService.record("CREATE_GROUP", groupName,
-                        "retryMaxTimes=" + config.getRetryMaxTimes(), 
"SUCCESS");
+            auditService.record("CREATE_GROUP", groupName,
+                    "retryMaxTimes=" + config.getRetryMaxTimes(), "SUCCESS");
 
-                group.setId(groupName);
-                return group;
-            } catch (BusinessException e) {
-                auditService.record("CREATE_GROUP", groupName, e.getMessage(), 
"FAILED");
-                throw e;
-            } catch (Exception e) {
-                auditService.record("CREATE_GROUP", groupName, e.getMessage(), 
"FAILED");
-                throw new BusinessException(500, "Failed to create consumer 
group: " + e.getMessage());
-            }
-        });
+            group.setId(groupName);
+            return group;
+        } catch (BusinessException e) {
+            auditService.record("CREATE_GROUP", groupName, e.getMessage(), 
"FAILED");
+            throw e;
+        } catch (Exception e) {
+            auditService.record("CREATE_GROUP", groupName, e.getMessage(), 
"FAILED");
+            throw new BusinessException(500, "Failed to create consumer group: 
" + e.getMessage());
+        }
     }
 
     @Override
-    public void deleteConsumerGroup(String name) {
+    public void deleteConsumerGroup(String instanceId, String name) {
+        if (StringUtils.hasText(instanceId)) {
+            runtimeAdminClientResolver.execute(instanceId, admin -> {
+                doDeleteConsumerGroup(admin, name);
+                return null;
+            });
+            return;
+        }
         adminFactory.execute(namesrvAddr(), null, admin -> {
-            try {
-                Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
-
-                for (String addr : brokerAddrs) {
-                    admin.deleteSubscriptionGroup(addr, name, true);
-                }
+            doDeleteConsumerGroup(admin, name);
+            return null;
+        });
+    }
 
-                // Delete from DB
-                groupMapper.delete(new 
LambdaQueryWrapper<RmqGroup>().eq(RmqGroup::getName, name));
+    private void doDeleteConsumerGroup(MQAdminExt admin, String name) {
+        try {
+            Set<String> brokerAddrs = getAllMasterBrokerAddrs(admin);
 
-                auditService.record("DELETE_GROUP", name, "", "SUCCESS");
-                return null;
-            } catch (BusinessException e) {
-                auditService.record("DELETE_GROUP", name, e.getMessage(), 
"FAILED");
-                throw e;
-            } catch (Exception e) {
-                auditService.record("DELETE_GROUP", name, e.getMessage(), 
"FAILED");
-                throw new BusinessException(500, "Failed to delete consumer 
group: " + e.getMessage());
+            for (String addr : brokerAddrs) {
+                admin.deleteSubscriptionGroup(addr, name, true);
             }
-        });
+
+            // Delete from DB
+            groupMapper.delete(new 
LambdaQueryWrapper<RmqGroup>().eq(RmqGroup::getName, name));
+
+            auditService.record("DELETE_GROUP", name, "", "SUCCESS");
+        } catch (BusinessException e) {
+            auditService.record("DELETE_GROUP", name, e.getMessage(), 
"FAILED");
+            throw e;
+        } catch (Exception e) {
+            auditService.record("DELETE_GROUP", name, e.getMessage(), 
"FAILED");
+            throw new BusinessException(500, "Failed to delete consumer group: 
" + e.getMessage());
+        }
     }
 
     @Override
-    public void resetOffset(String name, long timestamp, String topic) {
-        adminFactory.execute(namesrvAddr(), null, admin -> {
-            try {
-                admin.resetOffsetByTimestamp(getClusterName(admin), topic, 
name, timestamp, false);
-                auditService.record("RESET_OFFSET", name,
-                        "topic=" + topic + ", timestamp=" + timestamp, 
"SUCCESS");
-                return null;
-            } catch (Exception e) {
-                auditService.record("RESET_OFFSET", name, e.getMessage(), 
"FAILED");
-                throw new BusinessException(500, "Failed to reset offset: " + 
e.getMessage());
+    public void resetOffset(String instanceId, String name, long timestamp, 
String topic) {
+        try {
+            if (StringUtils.hasText(instanceId)) {
+                runtimeAdminClientResolver.execute(instanceId, admin -> {
+                    admin.resetOffsetByTimestamp(getClusterName(admin), topic, 
name, timestamp, false);
+                    return null;
+                });
+            } else {
+                adminFactory.execute(namesrvAddr(), null, admin -> {
+                    admin.resetOffsetByTimestamp(getClusterName(admin), topic, 
name, timestamp, false);
+                    return null;
+                });
             }
-        });
+            auditService.record("RESET_OFFSET", name,
+                    "instanceId=" + instanceId + ", topic=" + topic + ", 
timestamp=" + timestamp, "SUCCESS");
+        } catch (Exception e) {
+            auditService.record("RESET_OFFSET", name, e.getMessage(), 
"FAILED");
+            throw new BusinessException(500, "Failed to reset offset: " + 
e.getMessage());
+        }
     }
 
     // ── Helper methods ──────────────────────────────────────────────────
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
index b17703bb..06d43089 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProvider.java
@@ -337,74 +337,82 @@ public class RocketMQMetadataProvider implements 
MetadataProvider {
     }
 
     @Override
-    public List<QueueProgressVO> getGroupProgress(String name) {
+    public List<QueueProgressVO> getGroupProgress(String instanceId, String 
name) {
+        if (StringUtils.hasText(instanceId)) {
+            return runtimeAdminClientResolver.execute(instanceId, admin -> 
getGroupProgress(admin, name));
+        }
         if (!hasAdmin()) {
             return Collections.emptyList();
         }
+        return adminExecute(admin -> getGroupProgress(admin, name));
+    }
 
-        return adminExecute(admin -> {
-            try {
-                ConsumeStats stats = admin.examineConsumeStats(name);
-                if (stats == null || stats.getOffsetTable() == null) {
-                    return Collections.emptyList();
-                }
-
-                List<QueueProgressVO> progress = new ArrayList<>();
-                for (Map.Entry<MessageQueue, OffsetWrapper> entry : 
stats.getOffsetTable().entrySet()) {
-                    MessageQueue mq = entry.getKey();
-                    OffsetWrapper ow = entry.getValue();
-                    long diff = Math.max(0, ow.getBrokerOffset() - 
ow.getConsumerOffset());
-
-                    progress.add(QueueProgressVO.builder()
-                            .broker(mq.getBrokerName())
-                            .queueId(mq.getQueueId())
-                            .brokerOffset(ow.getBrokerOffset())
-                            .consumerOffset(ow.getConsumerOffset())
-                            .diffTotal(diff)
-                            .build());
-                }
-
-                progress.sort((a, b) -> {
-                    int cmp = a.getBroker().compareToIgnoreCase(b.getBroker());
-                    return cmp != 0 ? cmp : Integer.compare(a.getQueueId(), 
b.getQueueId());
-                });
-                return progress;
-            } catch (Exception e) {
-                log.warn("Failed to get progress for group {}: {}", name, 
e.getMessage());
+    private List<QueueProgressVO> getGroupProgress(MQAdminExt admin, String 
name) {
+        try {
+            ConsumeStats stats = admin.examineConsumeStats(name);
+            if (stats == null || stats.getOffsetTable() == null) {
                 return Collections.emptyList();
             }
-        });
+
+            List<QueueProgressVO> progress = new ArrayList<>();
+            for (Map.Entry<MessageQueue, OffsetWrapper> entry : 
stats.getOffsetTable().entrySet()) {
+                MessageQueue mq = entry.getKey();
+                OffsetWrapper ow = entry.getValue();
+                long diff = Math.max(0, ow.getBrokerOffset() - 
ow.getConsumerOffset());
+
+                progress.add(QueueProgressVO.builder()
+                        .broker(mq.getBrokerName())
+                        .queueId(mq.getQueueId())
+                        .brokerOffset(ow.getBrokerOffset())
+                        .consumerOffset(ow.getConsumerOffset())
+                        .diffTotal(diff)
+                        .build());
+            }
+
+            progress.sort((a, b) -> {
+                int cmp = a.getBroker().compareToIgnoreCase(b.getBroker());
+                return cmp != 0 ? cmp : Integer.compare(a.getQueueId(), 
b.getQueueId());
+            });
+            return progress;
+        } catch (Exception e) {
+            log.warn("Failed to get progress for group {}: {}", name, 
e.getMessage());
+            return Collections.emptyList();
+        }
     }
 
     @Override
-    public List<SubscriptionEntryVO> getGroupSubscriptions(String name) {
+    public List<SubscriptionEntryVO> getGroupSubscriptions(String instanceId, 
String name) {
+        if (StringUtils.hasText(instanceId)) {
+            return runtimeAdminClientResolver.execute(instanceId, admin -> 
getGroupSubscriptions(admin, name));
+        }
         if (!hasAdmin()) {
             return Collections.emptyList();
         }
+        return adminExecute(admin -> getGroupSubscriptions(admin, name));
+    }
 
-        return adminExecute(admin -> {
-            try {
-                ConsumerConnection conn = 
admin.examineConsumerConnectionInfo(name);
-                if (conn == null || conn.getSubscriptionTable() == null) {
-                    return Collections.emptyList();
-                }
-
-                List<SubscriptionEntryVO> subscriptions = new ArrayList<>();
-                for (Map.Entry<String, SubscriptionData> entry : 
conn.getSubscriptionTable().entrySet()) {
-                    SubscriptionData sd = entry.getValue();
-                    subscriptions.add(SubscriptionEntryVO.builder()
-                            .topic(sd.getTopic())
-                            .expression(sd.getSubString())
-                            .type(sd.getExpressionType())
-                            .filterMode(filterMode(sd.getExpressionType()))
-                            .build());
-                }
-                return subscriptions;
-            } catch (Exception e) {
-                log.warn("Failed to get subscriptions for group {}: {}", name, 
e.getMessage());
+    private List<SubscriptionEntryVO> getGroupSubscriptions(MQAdminExt admin, 
String name) {
+        try {
+            ConsumerConnection conn = 
admin.examineConsumerConnectionInfo(name);
+            if (conn == null || conn.getSubscriptionTable() == null) {
                 return Collections.emptyList();
             }
-        });
+
+            List<SubscriptionEntryVO> subscriptions = new ArrayList<>();
+            for (Map.Entry<String, SubscriptionData> entry : 
conn.getSubscriptionTable().entrySet()) {
+                SubscriptionData sd = entry.getValue();
+                subscriptions.add(SubscriptionEntryVO.builder()
+                        .topic(sd.getTopic())
+                        .expression(sd.getSubString())
+                        .type(sd.getExpressionType())
+                        .filterMode(filterMode(sd.getExpressionType()))
+                        .build());
+            }
+            return subscriptions;
+        } catch (Exception e) {
+            log.warn("Failed to get subscriptions for group {}: {}", name, 
e.getMessage());
+            return Collections.emptyList();
+        }
     }
 
     // ── Helper methods ──────────────────────────────────────────────────
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
index 09b9f718..55b5fd67 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
@@ -63,6 +63,7 @@ class ConsumerGroupControllerTest {
     @Test
     void createConsumerGroupShouldPassValidatedRequest() throws Exception {
         Map<String, Object> body = Map.of(
+                "instanceId", "instance-a",
                 "name", "cg-orders",
                 "clusterId", "cluster-a",
                 "retryMaxTimes", 8,
@@ -110,6 +111,7 @@ class ConsumerGroupControllerTest {
     @Test
     void createConsumerGroupShouldRejectNegativeRetryMaxTimes() throws 
Exception {
         Map<String, Object> body = Map.of(
+                "instanceId", "instance-a",
                 "name", "cg-orders",
                 "retryMaxTimes", -1
         );
@@ -155,9 +157,26 @@ class ConsumerGroupControllerTest {
                         
.value("org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService.run"));
     }
 
+    @Test
+    void groupRuntimeDiagnosticsShouldPassSelectedInstance() throws Exception {
+        when(metadataService.getGroupProgress("instance-a", 
"cg-orders")).thenReturn(List.of());
+        when(metadataService.getGroupSubscriptions("instance-a", 
"cg-orders")).thenReturn(List.of());
+
+        
mockMvc.perform(get("/api/groups/cg-orders/progress").param("instanceId", 
"instance-a"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200));
+        
mockMvc.perform(get("/api/groups/cg-orders/subscriptions").param("instanceId", 
"instance-a"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200));
+
+        verify(metadataService).getGroupProgress("instance-a", "cg-orders");
+        verify(metadataService).getGroupSubscriptions("instance-a", 
"cg-orders");
+    }
+
     @Test
     void resetOffsetShouldPassValidatedRequest() throws Exception {
         Map<String, Object> body = Map.of(
+                "instanceId", "instance-a",
                 "name", "cg-orders",
                 "topic", "orders",
                 "timestamp", 1784246400000L
@@ -170,7 +189,7 @@ class ConsumerGroupControllerTest {
                 .andExpect(jsonPath("$.code").value(200))
                 .andExpect(jsonPath("$.message").value("success"));
 
-        verify(metadataService).resetOffset(isNull(), eq("cg-orders"), 
eq(1784246400000L), eq("orders"));
+        verify(metadataService).resetOffset(eq("instance-a"), eq("cg-orders"), 
eq(1784246400000L), eq("orders"));
     }
 
     @Test
@@ -212,6 +231,7 @@ class ConsumerGroupControllerTest {
     @Test
     void resetOffsetShouldRejectMissingName() throws Exception {
         Map<String, Object> body = Map.of(
+                "instanceId", "instance-a",
                 "topic", "orders",
                 "timestamp", 1784246400000L
         );
@@ -229,6 +249,7 @@ class ConsumerGroupControllerTest {
     @Test
     void resetOffsetShouldRejectMissingTimestamp() throws Exception {
         Map<String, Object> body = Map.of(
+                "instanceId", "instance-a",
                 "name", "cg-orders",
                 "topic", "orders"
         );
@@ -246,6 +267,7 @@ class ConsumerGroupControllerTest {
     @Test
     void resetOffsetShouldRejectNonPositiveTimestamp() throws Exception {
         Map<String, Object> body = Map.of(
+                "instanceId", "instance-a",
                 "name", "cg-orders",
                 "topic", "orders",
                 "timestamp", 0L
@@ -264,6 +286,7 @@ class ConsumerGroupControllerTest {
     @Test
     void resetOffsetShouldRejectInvalidTimestampType() throws Exception {
         Map<String, Object> body = Map.of(
+                "instanceId", "instance-a",
                 "name", "cg-orders",
                 "topic", "orders",
                 "timestamp", "invalid"
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClientTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClientTest.java
index 0b7f1928..5626d4e3 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClientTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/NameSrvAdminClientTest.java
@@ -33,10 +33,10 @@ class NameSrvAdminClientTest {
         ConsumerGroupVO group = new ConsumerGroupVO();
         group.setName("cg-order");
 
-        assertUnavailable(() -> adminClient.getConsumerGroup("cg-order"));
+        assertUnavailable(() -> adminClient.getConsumerGroup("instance-a", 
"cg-order"));
         assertUnavailable(() -> adminClient.createConsumerGroup(group));
-        assertUnavailable(() -> adminClient.deleteConsumerGroup("cg-order"));
-        assertUnavailable(() -> adminClient.resetOffset("cg-order", 
1784246400000L, "order-topic"));
+        assertUnavailable(() -> adminClient.deleteConsumerGroup("instance-a", 
"cg-order"));
+        assertUnavailable(() -> adminClient.resetOffset("instance-a", 
"cg-order", 1784246400000L, "order-topic"));
     }
 
     private void assertUnavailable(ThrowableAssert.ThrowingCallable callable) {
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
index 4ac7f165..0ad5d522 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQAdminClientImplTest.java
@@ -26,11 +26,13 @@ import 
org.apache.rocketmq.remoting.protocol.body.ClusterInfo;
 import org.apache.rocketmq.remoting.protocol.route.BrokerData;
 import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.cluster.broker.RuntimeAdminClientResolver;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
 import org.apache.rocketmq.studio.instance.topic.TopicVO;
 import org.apache.rocketmq.studio.instance.topic.SendMessageDTO;
 import org.apache.rocketmq.studio.instance.topic.SendMessageVO;
 import org.apache.rocketmq.studio.ops.audit.AuditService;
+import org.apache.rocketmq.studio.persistence.entity.RmqGroup;
 import org.apache.rocketmq.studio.persistence.entity.RmqTopic;
 import org.apache.rocketmq.studio.persistence.mapper.RmqGroupMapper;
 import org.apache.rocketmq.studio.persistence.mapper.RmqTopicMapper;
@@ -59,6 +61,7 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
@@ -76,6 +79,8 @@ class RocketMQAdminClientImplTest {
     private RmqGroupMapper groupMapper;
     @Mock
     private AuditService auditService;
+    @Mock
+    private RuntimeAdminClientResolver runtimeAdminClientResolver;
 
     private RocketMQAdminClientImpl adminClient;
 
@@ -84,7 +89,8 @@ class RocketMQAdminClientImplTest {
         
lenient().when(properties.getNamesrvAddr()).thenReturn("10.0.0.1:9876");
         lenient().when(adminFactory.execute(anyString(), any(), 
any())).thenAnswer(invocation ->
                 
invocation.<MqAdminExtFactory.AdminAction<Object>>getArgument(2).apply(adminExt));
-        adminClient = new RocketMQAdminClientImpl(adminFactory, properties, 
topicMapper, groupMapper, auditService);
+        adminClient = new RocketMQAdminClientImpl(adminFactory, properties, 
topicMapper, groupMapper, auditService,
+                runtimeAdminClientResolver);
     }
 
     @Test
@@ -93,18 +99,44 @@ class RocketMQAdminClientImplTest {
                 .thenThrow(new 
MQClientException(ResponseCode.CONSUMER_NOT_ONLINE,
                         "Not found the consumer group connection"));
 
-        ConsumerGroupVO group = adminClient.getConsumerGroup("orders");
+        ConsumerGroupVO group = adminClient.getConsumerGroup(null, "orders");
 
         assertThat(group.getId()).isEqualTo("orders");
         assertThat(group.getOnlineInstances()).isZero();
     }
 
+    @Test
+    void getConsumerGroupUsesSelectedInstanceAdmin() throws Exception {
+        DefaultMQAdminExt selectedAdmin = 
org.mockito.Mockito.mock(DefaultMQAdminExt.class);
+        
when(runtimeAdminClientResolver.execute(org.mockito.ArgumentMatchers.eq("instance-a"),
 any()))
+                .thenAnswer(invocation -> {
+                    MqAdminExtFactory.AdminAction<?> action = 
invocation.getArgument(1);
+                    return action.apply(selectedAdmin);
+                });
+
+        ConsumerGroupVO group = adminClient.getConsumerGroup("instance-a", 
"cg-orders");
+
+        assertThat(group.getName()).isEqualTo("cg-orders");
+        
verify(runtimeAdminClientResolver).execute(org.mockito.ArgumentMatchers.eq("instance-a"),
 any());
+        verify(selectedAdmin).examineConsumerConnectionInfo("cg-orders");
+        verify(adminExt, never()).examineConsumerConnectionInfo(anyString());
+    }
+
+    @Test
+    void resetOffsetShouldUseSelectedInstanceRuntimeClient() {
+        adminClient.resetOffset("instance-a", "cg-orders", 1784246400000L, 
"orders");
+
+        
verify(runtimeAdminClientResolver).execute(org.mockito.ArgumentMatchers.eq("instance-a"),
 any());
+        verify(auditService).record("RESET_OFFSET", "cg-orders",
+                "instanceId=instance-a, topic=orders, 
timestamp=1784246400000", "SUCCESS");
+    }
+
     @Test
     void getConsumerGroupSurfacesAdminTimeout() throws Exception {
         when(adminExt.examineConsumerConnectionInfo("orders"))
                 .thenThrow(new RemotingTimeoutException("broker-0", 3_000));
 
-        assertThatThrownBy(() -> adminClient.getConsumerGroup("orders"))
+        assertThatThrownBy(() -> adminClient.getConsumerGroup(null, "orders"))
                 .isInstanceOf(BusinessException.class)
                 .hasMessageContaining("Failed to get consumer group");
     }
@@ -114,7 +146,7 @@ class RocketMQAdminClientImplTest {
         when(adminExt.examineConsumerConnectionInfo("orders"))
                 .thenThrow(new MQBrokerException(16, "ACL denied"));
 
-        assertThatThrownBy(() -> adminClient.getConsumerGroup("orders"))
+        assertThatThrownBy(() -> adminClient.getConsumerGroup(null, "orders"))
                 .isInstanceOf(BusinessException.class)
                 .hasMessageContaining("ACL denied");
     }
@@ -148,6 +180,60 @@ class RocketMQAdminClientImplTest {
         }
     }
 
+    @Test
+    void createConsumerGroupUsesSelectedInstanceAdmin() throws Exception {
+        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new 
MybatisConfiguration(), ""), RmqGroup.class);
+        DefaultMQAdminExt selectedAdmin = 
org.mockito.Mockito.mock(DefaultMQAdminExt.class);
+        ClusterInfo clusterInfo = new ClusterInfo();
+        clusterInfo.setClusterAddrTable(new HashMap<>(Map.of("cluster-1", new 
HashSet<>(List.of("broker-1")))));
+        BrokerData brokerData = new BrokerData();
+        brokerData.setBrokerName("broker-1");
+        brokerData.setBrokerAddrs(new HashMap<>(Map.of(0L, "10.0.0.1:10911")));
+        clusterInfo.setBrokerAddrTable(new HashMap<>(Map.of("broker-1", 
brokerData)));
+        when(selectedAdmin.examineBrokerClusterInfo()).thenReturn(clusterInfo);
+        when(groupMapper.selectOne(any())).thenReturn(null);
+        
doNothing().when(selectedAdmin).createAndUpdateSubscriptionGroupConfig(anyString(),
 any());
+        
when(runtimeAdminClientResolver.execute(org.mockito.ArgumentMatchers.eq("instance-a"),
 any()))
+                .thenAnswer(invocation -> {
+                    MqAdminExtFactory.AdminAction<?> action = 
invocation.getArgument(1);
+                    return action.apply(selectedAdmin);
+                });
+
+        ConsumerGroupVO group = new ConsumerGroupVO();
+        group.setName("cg-orders");
+        group.setInstanceId("instance-a");
+
+        adminClient.createConsumerGroup(group);
+
+        
verify(runtimeAdminClientResolver).execute(org.mockito.ArgumentMatchers.eq("instance-a"),
 any());
+        verify(selectedAdmin).createAndUpdateSubscriptionGroupConfig(
+                org.mockito.ArgumentMatchers.eq("10.0.0.1:10911"), any());
+        verify(adminExt, 
never()).createAndUpdateSubscriptionGroupConfig(anyString(), any());
+    }
+
+    @Test
+    void deleteConsumerGroupUsesSelectedInstanceAdmin() throws Exception {
+        DefaultMQAdminExt selectedAdmin = 
org.mockito.Mockito.mock(DefaultMQAdminExt.class);
+        ClusterInfo clusterInfo = new ClusterInfo();
+        BrokerData brokerData = new BrokerData();
+        brokerData.setBrokerName("broker-1");
+        brokerData.setBrokerAddrs(new HashMap<>(Map.of(0L, "10.0.0.1:10911")));
+        clusterInfo.setBrokerAddrTable(new HashMap<>(Map.of("broker-1", 
brokerData)));
+        when(selectedAdmin.examineBrokerClusterInfo()).thenReturn(clusterInfo);
+        doNothing().when(selectedAdmin).deleteSubscriptionGroup(anyString(), 
anyString(), org.mockito.ArgumentMatchers.anyBoolean());
+        
when(runtimeAdminClientResolver.execute(org.mockito.ArgumentMatchers.eq("instance-a"),
 any()))
+                .thenAnswer(invocation -> {
+                    MqAdminExtFactory.AdminAction<?> action = 
invocation.getArgument(1);
+                    return action.apply(selectedAdmin);
+                });
+
+        adminClient.deleteConsumerGroup("instance-a", "cg-orders");
+
+        
verify(runtimeAdminClientResolver).execute(org.mockito.ArgumentMatchers.eq("instance-a"),
 any());
+        verify(selectedAdmin).deleteSubscriptionGroup("10.0.0.1:10911", 
"cg-orders", true);
+        verify(adminExt, never()).deleteSubscriptionGroup(anyString(), 
anyString(), org.mockito.ArgumentMatchers.anyBoolean());
+    }
+
     @Test
     void sendMessageShouldNotFailWhenAuditRecordingFails() throws Exception {
         when(properties.getNamesrvAddr()).thenReturn("10.0.0.1:9876");
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
index 55991581..90d4607c 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/provider/apache/RocketMQMetadataProviderTest.java
@@ -19,6 +19,8 @@ package org.apache.rocketmq.studio.provider.apache;
 import org.apache.rocketmq.studio.cluster.broker.MqAdminExtFactory;
 import org.apache.rocketmq.studio.common.domain.enums.ConsumeType;
 import org.apache.rocketmq.studio.cluster.broker.RuntimeAdminClientResolver;
+import org.apache.rocketmq.studio.instance.group.QueueProgressVO;
+import org.apache.rocketmq.studio.instance.group.SubscriptionEntryVO;
 import org.apache.rocketmq.studio.instance.topic.BrokerRouteVO;
 import org.apache.rocketmq.studio.instance.topic.TopicConsumerVO;
 import org.apache.rocketmq.studio.instance.group.ConsumerGroupVO;
@@ -113,4 +115,18 @@ class RocketMQMetadataProviderTest {
         assertThat(provider.getTopicConsumers("instance-a", 
"orders")).containsExactlyElementsOf(consumers);
         verify(runtimeAdminClientResolver).execute(eq("instance-a"), any());
     }
+
+    @Test
+    void groupRuntimeDiagnosticsShouldUseSelectedInstanceRuntimeClient() {
+        List<QueueProgressVO> progress = 
List.of(QueueProgressVO.builder().broker("broker-a").build());
+        List<SubscriptionEntryVO> subscriptions = 
List.of(SubscriptionEntryVO.builder().topic("orders").build());
+        when(runtimeAdminClientResolver.execute(eq("instance-a"), any()))
+                .thenReturn(progress, subscriptions);
+        RocketMQMetadataProvider provider = newProvider();
+
+        assertThat(provider.getGroupProgress("instance-a", 
"cg-orders")).containsExactlyElementsOf(progress);
+        assertThat(provider.getGroupSubscriptions("instance-a", "cg-orders"))
+                .containsExactlyElementsOf(subscriptions);
+        verify(runtimeAdminClientResolver, 
org.mockito.Mockito.times(2)).execute(eq("instance-a"), any());
+    }
 }
diff --git a/web/src/api/consumerGroups.test.ts 
b/web/src/api/consumerGroups.test.ts
index c997a4d0..cd6f578b 100644
--- a/web/src/api/consumerGroups.test.ts
+++ b/web/src/api/consumerGroups.test.ts
@@ -65,19 +65,28 @@ describe('consumer groups API contract', () => {
     await expect(listConsumerGroups(params)).resolves.toEqual([group]);
   });
 
-  it('encodes consumer group names used in path segments', async () => {
+  it('encodes consumer group names and passes instance context for runtime 
queries', async () => {
     const groupName = '%RETRY%cg-order';
+    const instanceId = 'instance-a';
     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([]);
+    await expect(getConsumerProgress(groupName, 
instanceId)).resolves.toEqual([]);
+    await expect(getConsumerSubscriptions(groupName, 
instanceId)).resolves.toEqual([]);
+
+    expect(mock.history.get[1].params).toEqual({ instanceId });
+    expect(mock.history.get[2].params).toEqual({ instanceId });
   });
 
   it('unwraps detail records and sends numeric reset timestamps', async () => {
-    const reset = { name: group.name, topic: 'orders', timestamp: 
1784246400000 };
+    const reset = {
+      name: group.name,
+      instanceId: 'instance-a',
+      topic: 'orders',
+      timestamp: 1784246400000,
+    };
     mock.onGet('/groups/orders').reply(200, { code: 200, data: group });
     mock.onPost('/groups/reset-offset').reply((config) => {
       expect(JSON.parse(config.data)).toEqual(reset);
diff --git a/web/src/api/metadata.ts b/web/src/api/metadata.ts
index 7d63cccc..1d027812 100644
--- a/web/src/api/metadata.ts
+++ b/web/src/api/metadata.ts
@@ -204,9 +204,9 @@ export async function deleteConsumerGroup(name: string, 
instanceId?: string) {
 
 export interface ResetConsumerOffsetRequest {
   name: string;
+  instanceId?: string;
   timestamp: number;
   topic?: string;
-  instanceId?: string;
 }
 
 export async function resetConsumerOffset(data: ResetConsumerOffsetRequest) {
diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx 
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index c7758803..2d3b7e50 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -22,6 +22,7 @@ import type React from 'react';
 import { MemoryRouter } from 'react-router-dom';
 import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
 import type { ConsumerGroup } from '../../../api/metadata';
+import * as instanceService from '../../../services/instanceService';
 import { LangProvider } from '../../../i18n/LangContext';
 import * as consumerService from '../../../services/consumerService';
 import ConsumerPage from '../consumer';
@@ -106,6 +107,19 @@ const renderWithProviders = (ui: React.ReactElement) =>
 describe('Consumer page', () => {
   beforeEach(() => {
     vi.clearAllMocks();
+    vi.mocked(instanceService.listInstances).mockResolvedValue([
+      {
+        id: 'instance-1',
+        name: 'instance-1',
+        remark: '',
+        type: 'PROXY',
+        endpoint: '10.0.0.1:8080',
+        topicCount: 0,
+        consumerGroupCount: 0,
+        createdAt: '2026-01-01T00:00:00Z',
+        updatedAt: '2026-01-01T00:00:00Z',
+      },
+    ]);
     vi.mocked(consumerService.listConsumerGroups).mockResolvedValue([group]);
     vi.mocked(consumerService.createConsumerGroup).mockImplementation(
       async (data: Partial<ConsumerGroup>) =>
@@ -214,6 +228,39 @@ describe('Consumer page', () => {
     await waitFor(() => 
expect(screen.getAllByText('remote-topic').length).toBeGreaterThan(0));
   });
 
+  it('passes the selected instance to group diagnostics', async () => {
+    vi.mocked(instanceService.listInstances).mockResolvedValue([
+      {
+        id: 'instance-a',
+        name: 'Instance A',
+        remark: '',
+        type: 'DIRECT',
+        endpoint: '127.0.0.1:9876',
+        topicCount: 0,
+        consumerGroupCount: 0,
+        createdAt: '2026-07-23T00:00:00Z',
+        updatedAt: '2026-07-23T00:00:00Z',
+      },
+    ]);
+    vi.mocked(consumerService.listConsumerGroups).mockResolvedValue([
+      { ...group, instanceId: 'instance-a' },
+    ]);
+    const user = userEvent.setup();
+    renderWithProviders(<ConsumerPage />);
+
+    await user.click(await screen.findByRole('button', { name: /详情/ }));
+
+    await waitFor(() =>
+      expect(consumerService.getConsumerSubscriptions).toHaveBeenCalledWith(
+        'remote-cg',
+        'instance-a',
+      ),
+    );
+    await waitFor(() =>
+      
expect(consumerService.getConsumerProgress).toHaveBeenCalledWith('remote-cg', 
'instance-a'),
+    );
+  });
+
   it('highlights inconsistent subscriptions and refreshes the check result', 
async () => {
     vi.mocked(consumerService.getConsumerSubscriptions)
       .mockResolvedValueOnce([
diff --git a/web/src/pages/instance/consumer.tsx 
b/web/src/pages/instance/consumer.tsx
index 3bc792eb..636363b3 100644
--- a/web/src/pages/instance/consumer.tsx
+++ b/web/src/pages/instance/consumer.tsx
@@ -1405,11 +1405,11 @@ const ConsumerPage = () => {
           if (resetGroup) {
             setResetSubmitting(true);
             try {
-              await resetConsumerOffset({
-                name: resetGroup.name,
-                timestamp: resetTime.valueOf(),
-                instanceId: selectedInstanceId || undefined,
-              });
+                await resetConsumerOffset({
+                  name: resetGroup.name,
+                  instanceId: selectedInstanceId || undefined,
+                  timestamp: resetTime.valueOf(),
+                });
               message.success(
                 `${resetGroup.name} 消费位点已重置到 ${resetTime.format('YYYY-MM-DD 
HH:mm:ss')}`,
               );

Reply via email to