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 4ec7d37ac feat(instance): CLOUD type rename and one-click cloud 
instance import
4ec7d37ac is described below

commit 4ec7d37ac5e3b368bcd1f194f8eb73d05ff9e6b8
Author: lizhimins <[email protected]>
AuthorDate: Thu Aug 20 10:38:28 2026 +0800

    feat(instance): CLOUD type rename and one-click cloud instance import
    
    Rename the legacy PROXY instance type to CLOUD (displayed as 云服务),
    drop the all-proxy filter grouping, and reject CLOUD for manually
    created APACHE instances. Add one-click import: POST
    /api/instances/import-cloud walks every catalog region of a credential
    and imports all instances idempotently, naming them by cloud instance
    id and auto-filling remarks from the cloud detail; the add-instance
    modal shows the button in the footer left of 取消.
---
 .../studio/common/domain/enums/InstanceType.java   |  12 +-
 .../CloudImportResultVO.java}                      |  38 +--
 .../ImportCloudInstancesDTO.java}                  |  24 +-
 .../studio/instance/InstanceController.java        |   5 +
 .../rocketmq/studio/instance/InstanceService.java  |  92 +++++++-
 .../instance/MybatisPlusInstanceRepository.java    |  15 +-
 .../provider/apache/RocketMQDashboardProvider.java |   2 +-
 server/src/main/resources/db/schema.sql            |   2 +-
 .../instance/InstanceCapabilityServiceTest.java    |   4 +-
 .../studio/instance/InstanceControllerTest.java    |  18 +-
 .../studio/instance/InstanceServiceTest.java       | 214 ++++++++++++++---
 .../MybatisPlusInstanceRepositoryTest.java         |  31 ++-
 web/src/api/instance.test.ts                       |   8 +-
 web/src/api/instance.ts                            |  14 +-
 web/src/hooks/useInstanceFilter.test.tsx           |   2 +-
 web/src/i18n/translations.ts                       |   1 -
 web/src/layouts/MainLayout.test.tsx                |   2 +-
 .../pages/instance/__tests__/ConsumerPage.test.tsx |   6 +-
 .../pages/instance/__tests__/InstancePage.test.tsx |  57 ++++-
 .../pages/instance/__tests__/TopicPage.test.tsx    |  16 +-
 web/src/pages/instance/index.tsx                   | 260 +++++++++++++--------
 web/src/pages/instance/topic.tsx                   |   6 +-
 web/src/services/instanceService.test.ts           |  11 +-
 web/src/services/instanceService.ts                |  19 +-
 24 files changed, 606 insertions(+), 253 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
 
b/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
index 4afd4c880..a0716f70d 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
@@ -18,17 +18,9 @@
 package org.apache.rocketmq.studio.common.domain.enums;
 
 public enum InstanceType {
-    /** Legacy generic Proxy value retained for persisted and cloud-managed 
instances. */
-    PROXY,
+    /** Cloud-managed (vendor-hosted) instance selected from the vendor 
catalog. */
+    CLOUD,
     PROXY_LOCAL,
     PROXY_CLUSTER,
     DIRECT;
-
-    public boolean isProxy() {
-        return this != DIRECT;
-    }
-
-    public InstanceType normalizeApacheType() {
-        return this == PROXY ? PROXY_CLUSTER : this;
-    }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/CloudImportResultVO.java
similarity index 66%
copy from 
server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/instance/CloudImportResultVO.java
index 4afd4c880..5f453923f 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/CloudImportResultVO.java
@@ -15,20 +15,26 @@
  * limitations under the License.
  */
 
-package org.apache.rocketmq.studio.common.domain.enums;
-
-public enum InstanceType {
-    /** Legacy generic Proxy value retained for persisted and cloud-managed 
instances. */
-    PROXY,
-    PROXY_LOCAL,
-    PROXY_CLUSTER,
-    DIRECT;
-
-    public boolean isProxy() {
-        return this != DIRECT;
-    }
-
-    public InstanceType normalizeApacheType() {
-        return this == PROXY ? PROXY_CLUSTER : this;
-    }
+package org.apache.rocketmq.studio.instance;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CloudImportResultVO {
+
+    private int discovered;
+
+    private int imported;
+
+    private int skipped;
+
+    private List<String> failed;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/ImportCloudInstancesDTO.java
similarity index 66%
copy from 
server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/instance/ImportCloudInstancesDTO.java
index 4afd4c880..90c7eab08 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/common/domain/enums/InstanceType.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/ImportCloudInstancesDTO.java
@@ -15,20 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.rocketmq.studio.common.domain.enums;
+package org.apache.rocketmq.studio.instance;
 
-public enum InstanceType {
-    /** Legacy generic Proxy value retained for persisted and cloud-managed 
instances. */
-    PROXY,
-    PROXY_LOCAL,
-    PROXY_CLUSTER,
-    DIRECT;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 
-    public boolean isProxy() {
-        return this != DIRECT;
-    }
+@Data
+public class ImportCloudInstancesDTO {
 
-    public InstanceType normalizeApacheType() {
-        return this == PROXY ? PROXY_CLUSTER : this;
-    }
+    @NotNull
+    private InstanceVendor vendor;
+
+    @NotNull
+    private Long credentialId;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
index d3dbb0ed2..2d271b5a1 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
@@ -58,6 +58,11 @@ public class InstanceController {
         return 
Result.ok(instanceService.createInstance(request.toInstanceVO()));
     }
 
+    @PostMapping("/import-cloud")
+    public Result<CloudImportResultVO> importCloudInstances(@Valid 
@RequestBody ImportCloudInstancesDTO request) {
+        return 
Result.ok(instanceService.importCloudInstances(request.getVendor(), 
request.getCredentialId()));
+    }
+
     @PostMapping("/update")
     public Result<InstanceVO> updateInstance(@Valid @RequestBody 
UpdateInstanceDTO request) {
         InstanceVO vo = request.toInstanceVO();
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
index 00c0db05a..40f8c8cfb 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceService.java
@@ -26,7 +26,10 @@ import 
org.apache.rocketmq.studio.audit.OperationAuditService;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceType;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.provider.CloudCatalogProvider;
 import org.apache.rocketmq.studio.provider.CloudInstanceDetailVO;
+import org.apache.rocketmq.studio.provider.CloudInstanceOptionVO;
+import org.apache.rocketmq.studio.provider.CloudRegionVO;
 import org.apache.rocketmq.studio.provider.InstanceProvider;
 import org.apache.rocketmq.studio.provider.InstanceProviderRegistry;
 import org.apache.rocketmq.studio.settings.DataSourceVO;
@@ -116,6 +119,78 @@ public class InstanceService {
         return saved;
     }
 
+    /**
+     * Imports every cloud instance visible to the credential by walking all 
catalog regions.
+     * Remarks are resolved from the cloud instance detail during creation. 
Instances whose
+     * resolved name already exists are skipped; per-instance failures are 
collected instead
+     * of aborting the batch.
+     */
+    public CloudImportResultVO importCloudInstances(InstanceVendor vendor, 
Long credentialId) {
+        if (vendor == null || vendor == InstanceVendor.APACHE) {
+            throw new BusinessException(400, "Import is only supported for 
cloud vendors");
+        }
+        if (credentialId == null) {
+            throw new BusinessException(400, "credentialId is required");
+        }
+        CloudCredentialVO credential = 
cloudCredentialRepository.findById(credentialId)
+                .orElseThrow(() -> new BusinessException(404, "Cloud 
credential not found: " + credentialId));
+        if (credential.getVendor() != vendor) {
+            throw new BusinessException(400, "Cloud credential vendor does not 
match " + vendor);
+        }
+        CloudCatalogProvider catalog = providerRegistry.catalogFor(vendor);
+
+        int discovered = 0;
+        int imported = 0;
+        int skipped = 0;
+        List<String> failed = new ArrayList<>();
+        for (CloudRegionVO region : catalog.listRegions(credentialId)) {
+            if (region == null || !StringUtils.hasText(region.getRegionId())) {
+                continue;
+            }
+            List<CloudInstanceOptionVO> options;
+            try {
+                options = catalog.listCloudInstances(credentialId, 
region.getRegionId(), null);
+            } catch (BusinessException ex) {
+                failed.add(region.getRegionId() + ": " + ex.getMessage());
+                continue;
+            }
+            for (CloudInstanceOptionVO option : options) {
+                if (option == null || 
!StringUtils.hasText(option.getInstanceId())) {
+                    continue;
+                }
+                discovered++;
+                InstanceVO request = InstanceVO.builder()
+                        .vendor(vendor)
+                        .credentialId(credentialId)
+                        .regionId(region.getRegionId())
+                        .cloudInstanceId(option.getInstanceId())
+                        .name(option.getInstanceId())
+                        .build();
+                try {
+                    createInstance(request);
+                    imported++;
+                } catch (BusinessException ex) {
+                    if (ex.getMessage() != null && 
ex.getMessage().startsWith("Instance name already exists")) {
+                        skipped++;
+                    } else {
+                        failed.add(option.getInstanceId() + ": " + 
ex.getMessage());
+                    }
+                }
+            }
+        }
+        log.info("Cloud import finished: vendor={}, credentialId={}, 
discovered={}, imported={}, skipped={}, failed={}",
+                vendor, credentialId, discovered, imported, skipped, 
failed.size());
+        recordAudit("IMPORT_CLOUD_INSTANCES", "INSTANCE", 
String.valueOf(credentialId), null,
+                "vendor=" + vendor + ", imported=" + imported + ", skipped=" + 
skipped
+                        + ", failed=" + failed.size());
+        return CloudImportResultVO.builder()
+                .discovered(discovered)
+                .imported(imported)
+                .skipped(skipped)
+                .failed(failed)
+                .build();
+    }
+
     private void requireUniqueInstanceName(String name, Long excludeId) {
         if (!StringUtils.hasText(name)) {
             return;
@@ -159,7 +234,9 @@ public class InstanceService {
         if (instance.getType() == null) {
             throw new BusinessException(400, "InstanceVO type is required");
         }
-        instance.setType(instance.getType().normalizeApacheType());
+        if (instance.getType() == InstanceType.CLOUD) {
+            throw new BusinessException(400, "CLOUD type is reserved for 
vendor-managed instances");
+        }
     }
 
     /**
@@ -189,12 +266,14 @@ public class InstanceService {
                     "Cloud instance details unavailable: " + 
instance.getCloudInstanceId());
         }
         if (!StringUtils.hasText(instance.getName())) {
-            instance.setName(detail.getInstanceName() != null && 
!detail.getInstanceName().isBlank()
-                    ? detail.getInstanceName() : detail.getInstanceId());
+            instance.setName(detail.getInstanceId());
         }
         instance.setName(requireInstanceName(instance.getName()));
-        instance.setType(InstanceType.PROXY);
+        instance.setType(InstanceType.CLOUD);
         instance.setEndpoint(resolveEndpoint(detail));
+        if (!StringUtils.hasText(instance.getRemark()) && 
StringUtils.hasText(detail.getRemark())) {
+            instance.setRemark(detail.getRemark());
+        }
     }
 
     private String resolveEndpoint(CloudInstanceDetailVO detail) {
@@ -275,7 +354,10 @@ public class InstanceService {
         }
         if (!cloudInstance) {
             if (instance.getType() != null) {
-                updated.setType(instance.getType().normalizeApacheType());
+                if (instance.getType() == InstanceType.CLOUD) {
+                    throw new BusinessException(400, "CLOUD type is reserved 
for vendor-managed instances");
+                }
+                updated.setType(instance.getType());
             }
             if (instance.getEndpoint() != null) {
                 
updated.setEndpoint(requireValidEndpoint(instance.getEndpoint()));
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
index 422e8643a..1452fbbf1 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepository.java
@@ -33,7 +33,6 @@ import 
org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 import java.util.Optional;
-import java.util.stream.Stream;
 
 @RequiredArgsConstructor
 @Repository
@@ -55,7 +54,7 @@ public class MybatisPlusInstanceRepository implements 
InstanceRepository {
     public List<InstanceVO> findByType(InstanceType type) {
         return instanceMapper.selectList(
                 new QueryWrapper<RmqInstance>()
-                        .in("type", typeNamesForFilter(type))
+                        .eq("type", type.name())
                         .orderByAsc("id")).stream()
                 .map(this::toVO)
                 .toList();
@@ -77,7 +76,7 @@ public class MybatisPlusInstanceRepository implements 
InstanceRepository {
     public List<InstanceVO> findByTypeAndSearch(InstanceType type, String 
keyword) {
         return instanceMapper.selectList(
                 new QueryWrapper<RmqInstance>()
-                        .in("type", typeNamesForFilter(type))
+                        .eq("type", type.name())
                         .and(w -> w.like("name", keyword)
                                 .or().like("endpoint", keyword)
                                 .or().like("remark", keyword))
@@ -86,16 +85,6 @@ public class MybatisPlusInstanceRepository implements 
InstanceRepository {
                 .toList();
     }
 
-    private List<String> typeNamesForFilter(InstanceType type) {
-        if (type == InstanceType.PROXY) {
-            return Stream.of(InstanceType.values())
-                    .filter(InstanceType::isProxy)
-                    .map(Enum::name)
-                    .toList();
-        }
-        return List.of(type.name());
-    }
-
     @Override
     public Optional<InstanceVO> findById(Long id) {
         if (id == null) {
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
index 08fd392b7..902b760c8 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/provider/apache/RocketMQDashboardProvider.java
@@ -334,7 +334,7 @@ public class RocketMQDashboardProvider implements 
DashboardProvider {
         return switch (instance.getType()) {
             case DIRECT -> ClusterType.V4_DIRECT;
             case PROXY_LOCAL -> ClusterType.V5_PROXY_LOCAL;
-            case PROXY, PROXY_CLUSTER -> ClusterType.V5_PROXY_CLUSTER;
+            case CLOUD, PROXY_CLUSTER -> ClusterType.V5_PROXY_CLUSTER;
         };
     }
 
diff --git a/server/src/main/resources/db/schema.sql 
b/server/src/main/resources/db/schema.sql
index 74063aa21..8f2a67eed 100644
--- a/server/src/main/resources/db/schema.sql
+++ b/server/src/main/resources/db/schema.sql
@@ -63,7 +63,7 @@ CREATE TABLE IF NOT EXISTS rmq_instance (
   `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP COMMENT '修改时间',
   name VARCHAR(128) NOT NULL,
   remark VARCHAR(255),
-  type VARCHAR(32) NOT NULL COMMENT 'PROXY/PROXY_LOCAL/PROXY_CLUSTER/DIRECT',
+  type VARCHAR(32) NOT NULL COMMENT 'CLOUD/PROXY_LOCAL/PROXY_CLUSTER/DIRECT',
   endpoint VARCHAR(512) NOT NULL,
   vendor VARCHAR(32),
   cloud_instance_id VARCHAR(128),
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceCapabilityServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceCapabilityServiceTest.java
index 428580186..b22cfdfd4 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceCapabilityServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceCapabilityServiceTest.java
@@ -57,7 +57,7 @@ class InstanceCapabilityServiceTest {
         InstanceVO instance = InstanceVO.builder()
                 .name("cloud-1")
                 .vendor(InstanceVendor.ALIYUN)
-                .type(InstanceType.PROXY)
+                .type(InstanceType.CLOUD)
                 .build();
         instance.setId(1L);
         
when(instanceRepository.findById(1L)).thenReturn(Optional.of(instance));
@@ -70,7 +70,7 @@ class InstanceCapabilityServiceTest {
 
         assertThat(result.instanceId()).isEqualTo("cloud-1");
         assertThat(result.vendor()).isEqualTo(InstanceVendor.ALIYUN);
-        assertThat(result.accessType()).isEqualTo(InstanceType.PROXY);
+        assertThat(result.accessType()).isEqualTo(InstanceType.CLOUD);
         assertThat(result.capabilities()).containsExactly(
                 InstanceCapability.TOPIC_MANAGEMENT,
                 InstanceCapability.MESSAGE_QUERY);
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
index 2c87ce067..689494985 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
@@ -65,7 +65,7 @@ class InstanceControllerTest {
 
     @Test
     void listInstancesShouldReturnAllInstances() throws Exception {
-        InstanceVO inst = buildInstance(1L, "production-proxy", 
InstanceType.PROXY, "10.0.1.1:8080");
+        InstanceVO inst = buildInstance(1L, "production-proxy", 
InstanceType.PROXY_CLUSTER, "10.0.1.1:8080");
 
         when(instanceService.listInstances(isNull(), 
isNull())).thenReturn(List.of(inst));
 
@@ -75,28 +75,28 @@ class InstanceControllerTest {
                 .andExpect(jsonPath("$.data").isArray())
                 .andExpect(jsonPath("$.data[0].id").value(1))
                 
.andExpect(jsonPath("$.data[0].name").value("production-proxy"))
-                .andExpect(jsonPath("$.data[0].type").value("PROXY"))
+                .andExpect(jsonPath("$.data[0].type").value("PROXY_CLUSTER"))
                 
.andExpect(jsonPath("$.data[0].endpoint").value("10.0.1.1:8080"));
     }
 
     @Test
     void listInstancesShouldFilterByType() throws Exception {
-        InstanceVO inst = buildInstance(1L, "proxy-1", InstanceType.PROXY, 
"10.0.1.1:8080");
+        InstanceVO inst = buildInstance(1L, "proxy-1", InstanceType.CLOUD, 
"10.0.1.1:8080");
 
-        when(instanceService.listInstances(eq(InstanceType.PROXY), 
isNull())).thenReturn(List.of(inst));
+        when(instanceService.listInstances(eq(InstanceType.CLOUD), 
isNull())).thenReturn(List.of(inst));
 
         mockMvc.perform(get("/api/instances")
-                        .param("type", "PROXY"))
+                        .param("type", "CLOUD"))
                 .andExpect(status().isOk())
                 .andExpect(jsonPath("$.data").isArray())
-                .andExpect(jsonPath("$.data[0].type").value("PROXY"));
+                .andExpect(jsonPath("$.data[0].type").value("CLOUD"));
 
-        verify(instanceService).listInstances(eq(InstanceType.PROXY), 
isNull());
+        verify(instanceService).listInstances(eq(InstanceType.CLOUD), 
isNull());
     }
 
     @Test
     void listInstancesShouldFilterBySearch() throws Exception {
-        InstanceVO inst = buildInstance(1L, "production", InstanceType.PROXY, 
"10.0.1.1:8080");
+        InstanceVO inst = buildInstance(1L, "production", 
InstanceType.PROXY_CLUSTER, "10.0.1.1:8080");
 
         when(instanceService.listInstances(isNull(), 
eq("prod"))).thenReturn(List.of(inst));
 
@@ -177,7 +177,7 @@ class InstanceControllerTest {
         InstanceVO updated = InstanceVO.builder()
                 .name("updated-name")
                 .endpoint("10.0.1.1:8080")
-                .type(InstanceType.PROXY)
+                .type(InstanceType.PROXY_CLUSTER)
                 .build();
         updated.setId(1L);
         updated.setGmtCreate(LocalDateTime.of(2026, 1, 1, 0, 0));
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
index 5ce91ec65..0d5474f30 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceServiceTest.java
@@ -26,12 +26,15 @@ import 
org.apache.rocketmq.studio.common.domain.enums.InstanceVendor;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.apache.rocketmq.studio.provider.CloudCatalogProvider;
 import org.apache.rocketmq.studio.provider.CloudInstanceDetailVO;
+import org.apache.rocketmq.studio.provider.CloudInstanceOptionVO;
+import org.apache.rocketmq.studio.provider.CloudRegionVO;
 import org.apache.rocketmq.studio.provider.InstanceProviderRegistry;
 import org.apache.rocketmq.studio.provider.InstanceProvider;
 import org.apache.rocketmq.studio.settings.DataSourceVO;
 import org.apache.rocketmq.studio.settings.SettingsRepository;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -146,15 +149,15 @@ class InstanceServiceTest {
     @Test
     void listInstancesShouldFilterByType() {
         List<InstanceVO> instances = List.of(
-                
InstanceVO.builder().name("proxy-1").type(InstanceType.PROXY).build()
+                
InstanceVO.builder().name("proxy-1").type(InstanceType.CLOUD).build()
         );
-        
when(instanceRepository.findByType(InstanceType.PROXY)).thenReturn(instances);
+        
when(instanceRepository.findByType(InstanceType.CLOUD)).thenReturn(instances);
         
when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
-        List<InstanceVO> result = 
instanceService.listInstances(InstanceType.PROXY, null);
+        List<InstanceVO> result = 
instanceService.listInstances(InstanceType.CLOUD, null);
 
         assertThat(result).hasSize(1);
-        verify(instanceRepository).findByType(InstanceType.PROXY);
+        verify(instanceRepository).findByType(InstanceType.CLOUD);
     }
 
     @Test
@@ -188,29 +191,29 @@ class InstanceServiceTest {
     @Test
     void listInstancesShouldFilterByTypeAndSearch() {
         List<InstanceVO> instances = List.of(
-                
InstanceVO.builder().name("production-proxy").type(InstanceType.PROXY).build()
+                
InstanceVO.builder().name("production-proxy").type(InstanceType.CLOUD).build()
         );
-        when(instanceRepository.findByTypeAndSearch(InstanceType.PROXY, 
"prod")).thenReturn(instances);
+        when(instanceRepository.findByTypeAndSearch(InstanceType.CLOUD, 
"prod")).thenReturn(instances);
         
when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
-        List<InstanceVO> result = 
instanceService.listInstances(InstanceType.PROXY, "prod");
+        List<InstanceVO> result = 
instanceService.listInstances(InstanceType.CLOUD, "prod");
 
         assertThat(result).hasSize(1);
-        verify(instanceRepository).findByTypeAndSearch(InstanceType.PROXY, 
"prod");
+        verify(instanceRepository).findByTypeAndSearch(InstanceType.CLOUD, 
"prod");
     }
 
     @Test
     void listInstancesShouldTrimSearchKeywordWhenFilteringByType() {
         List<InstanceVO> instances = List.of(
-                
InstanceVO.builder().name("production-proxy").type(InstanceType.PROXY).build()
+                
InstanceVO.builder().name("production-proxy").type(InstanceType.CLOUD).build()
         );
-        when(instanceRepository.findByTypeAndSearch(InstanceType.PROXY, 
"prod")).thenReturn(instances);
+        when(instanceRepository.findByTypeAndSearch(InstanceType.CLOUD, 
"prod")).thenReturn(instances);
         
when(providerRegistry.forVendor(InstanceVendor.APACHE)).thenReturn(instanceProvider);
 
-        List<InstanceVO> result = 
instanceService.listInstances(InstanceType.PROXY, " prod ");
+        List<InstanceVO> result = 
instanceService.listInstances(InstanceType.CLOUD, " prod ");
 
         assertThat(result).hasSize(1);
-        verify(instanceRepository).findByTypeAndSearch(InstanceType.PROXY, 
"prod");
+        verify(instanceRepository).findByTypeAndSearch(InstanceType.CLOUD, 
"prod");
     }
 
     @Test
@@ -278,7 +281,7 @@ class InstanceServiceTest {
         InstanceVO input = InstanceVO.builder()
                 .name("new-instance")
                 .endpoint("10.0.1.1:8080")
-                .type(InstanceType.PROXY)
+                .type(InstanceType.PROXY_CLUSTER)
                 .build();
 
         
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> {
@@ -362,7 +365,7 @@ class InstanceServiceTest {
 
     @Test
     void createInstanceShouldTrimEndpointBeforeSaving() {
-        InstanceVO input = 
InstanceVO.builder().name("valid-name").type(InstanceType.PROXY)
+        InstanceVO input = 
InstanceVO.builder().name("valid-name").type(InstanceType.PROXY_CLUSTER)
                 .endpoint("  namesrv:9876  ").build();
         
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
 
@@ -371,7 +374,7 @@ class InstanceServiceTest {
 
     @Test
     void createInstanceShouldTrimNameBeforeSaving() {
-        InstanceVO input = InstanceVO.builder().name("  production  
").type(InstanceType.PROXY)
+        InstanceVO input = InstanceVO.builder().name("  production  
").type(InstanceType.PROXY_CLUSTER)
                 .endpoint("namesrv:9876").build();
         
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
 
@@ -380,7 +383,7 @@ class InstanceServiceTest {
 
     @Test
     void 
createApacheInstanceShouldTrimAndPersistOnlyAdminCredentialReference() {
-        InstanceVO input = 
InstanceVO.builder().name("production").type(InstanceType.PROXY)
+        InstanceVO input = 
InstanceVO.builder().name("production").type(InstanceType.PROXY_CLUSTER)
                 .endpoint("namesrv:9876").adminCredentialRef(" 
production-admin ").build();
         
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
 
@@ -391,7 +394,7 @@ class InstanceServiceTest {
 
     @Test
     void 
updateApacheInstanceShouldReleaseCachedClientWhenCredentialReferenceChanges() {
-        InstanceVO existing = 
InstanceVO.builder().name("production").type(InstanceType.PROXY)
+        InstanceVO existing = 
InstanceVO.builder().name("production").type(InstanceType.PROXY_CLUSTER)
                 
.endpoint("namesrv:9876").adminCredentialRef("credential-a").build();
         existing.setId(1L);
         InstanceVO update = 
InstanceVO.builder().adminCredentialRef("credential-b").build();
@@ -412,7 +415,7 @@ class InstanceServiceTest {
         InstanceVO existing = InstanceVO.builder()
                 .name("old-name")
                 .endpoint("10.0.1.1:8080")
-                .type(InstanceType.PROXY)
+                .type(InstanceType.PROXY_CLUSTER)
                 .remark("old remark")
                 .topicCount(7)
                 .consumerGroupCount(3)
@@ -434,7 +437,7 @@ class InstanceServiceTest {
 
         assertThat(result.getName()).isEqualTo("old-name");
         assertThat(result.getEndpoint()).isEqualTo("10.0.1.1:8080");
-        assertThat(result.getType()).isEqualTo(InstanceType.PROXY);
+        assertThat(result.getType()).isEqualTo(InstanceType.PROXY_CLUSTER);
         assertThat(result.getRemark()).isEqualTo("new remark");
         assertThat(result.getTopicCount()).isEqualTo(7);
         assertThat(result.getConsumerGroupCount()).isEqualTo(3);
@@ -446,7 +449,7 @@ class InstanceServiceTest {
         assertThat(existing.getRemark()).isEqualTo("old remark");
         assertThat(existing.getGmtModified()).isEqualTo(originalUpdatedAt);
         verify(operationAuditService).record(eq("UPDATE_INSTANCE"), 
eq("INSTANCE"), eq("1"), eq(null),
-                eq("name=old-name, vendor=APACHE, type=PROXY"), eq("SUCCESS"), 
eq(null));
+                eq("name=old-name, vendor=APACHE, type=PROXY_CLUSTER"), 
eq("SUCCESS"), eq(null));
     }
 
     @Test
@@ -470,7 +473,7 @@ class InstanceServiceTest {
         InstanceVO stored = InstanceVO.builder()
                 .name("old-name")
                 .remark("old remark")
-                .type(InstanceType.PROXY)
+                .type(InstanceType.PROXY_CLUSTER)
                 .endpoint("10.0.1.1:8080")
                 .topicCount(7)
                 .consumerGroupCount(3)
@@ -497,7 +500,7 @@ class InstanceServiceTest {
 
         assertThat(stored.getName()).isEqualTo("old-name");
         assertThat(stored.getRemark()).isEqualTo("old remark");
-        assertThat(stored.getType()).isEqualTo(InstanceType.PROXY);
+        assertThat(stored.getType()).isEqualTo(InstanceType.PROXY_CLUSTER);
         assertThat(stored.getEndpoint()).isEqualTo("10.0.1.1:8080");
         assertThat(stored.getTopicCount()).isEqualTo(7);
         assertThat(stored.getConsumerGroupCount()).isEqualTo(3);
@@ -817,7 +820,7 @@ class InstanceServiceTest {
         InstanceVO instance = InstanceVO.builder()
                 .name("inst")
                 .endpoint("10.0.0.1:8080")
-                .type(InstanceType.PROXY)
+                .type(InstanceType.PROXY_CLUSTER)
                 .build();
         
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
 
@@ -857,6 +860,39 @@ class InstanceServiceTest {
         verify(instanceRepository, never()).save(any(InstanceVO.class));
     }
 
+    @Test
+    void createApacheInstanceShouldRejectCloudTypeTest() {
+        InstanceVO instance = InstanceVO.builder()
+                .name("inst")
+                .endpoint("10.0.0.1:8080")
+                .type(InstanceType.CLOUD)
+                .build();
+
+        assertThatThrownBy(() -> instanceService.createInstance(instance))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("CLOUD type is reserved for vendor-managed 
instances")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(400));
+
+        verify(instanceRepository, never()).save(any(InstanceVO.class));
+    }
+
+    @Test
+    void updateApacheInstanceShouldRejectCloudTypeTest() {
+        InstanceVO existing = 
InstanceVO.builder().name("production").type(InstanceType.PROXY_CLUSTER)
+                .endpoint("namesrv:9876").build();
+        existing.setId(1L);
+        InstanceVO update = 
InstanceVO.builder().type(InstanceType.CLOUD).build();
+        update.setId(1L);
+        
when(instanceRepository.findById(1L)).thenReturn(Optional.of(existing));
+
+        assertThatThrownBy(() -> instanceService.updateInstance(update))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("CLOUD type is reserved for vendor-managed 
instances")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(400));
+
+        verify(instanceRepository, never()).save(any(InstanceVO.class));
+    }
+
     @Test
     void createInstanceShouldRejectManualEndpointForAliyunTest() {
         InstanceVO instance = InstanceVO.builder()
@@ -926,9 +962,131 @@ class InstanceServiceTest {
 
         InstanceVO created = instanceService.createInstance(instance);
 
-        assertThat(created.getName()).isEqualTo("prod-mq");
+        assertThat(created.getName()).isEqualTo("rmq-cn-xxx");
         assertThat(created.getEndpoint()).isEqualTo("vpc:8080");
-        assertThat(created.getType()).isEqualTo(InstanceType.PROXY);
+        assertThat(created.getType()).isEqualTo(InstanceType.CLOUD);
+    }
+
+    @Test
+    void createInstanceShouldAutoFillRemarkFromCloudDetailTest() {
+        InstanceVO instance = InstanceVO.builder()
+                .vendor(InstanceVendor.ALIYUN)
+                .credentialId(1L)
+                .cloudInstanceId("rmq-cn-xxx")
+                .regionId("cn-hangzhou")
+                .build();
+        CloudCredentialVO credential = new CloudCredentialVO();
+        credential.setId(1L);
+        credential.setVendor(InstanceVendor.ALIYUN);
+        
when(cloudCredentialRepository.findById(1L)).thenReturn(Optional.of(credential));
+        CloudCatalogProvider catalog = 
org.mockito.Mockito.mock(CloudCatalogProvider.class);
+        CloudInstanceDetailVO detail = new CloudInstanceDetailVO();
+        detail.setInstanceId("rmq-cn-xxx");
+        detail.setInstanceName("prod-mq");
+        detail.setRemark("prod-link");
+        detail.setEndpoints(List.of(new 
CloudInstanceDetailVO.CloudEndpoint("TCP_VPC", "vpc:8080")));
+        
when(providerRegistry.catalogFor(InstanceVendor.ALIYUN)).thenReturn(catalog);
+        when(catalog.getCloudInstance(1L, "cn-hangzhou", 
"rmq-cn-xxx")).thenReturn(detail);
+        
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
+
+        InstanceVO created = instanceService.createInstance(instance);
+
+        assertThat(created.getRemark()).isEqualTo("prod-link");
+    }
+
+    @Test
+    void createInstanceShouldKeepExplicitRemarkOverCloudDetailTest() {
+        InstanceVO instance = InstanceVO.builder()
+                .vendor(InstanceVendor.ALIYUN)
+                .credentialId(1L)
+                .cloudInstanceId("rmq-cn-xxx")
+                .regionId("cn-hangzhou")
+                .remark("user-remark")
+                .build();
+        CloudCredentialVO credential = new CloudCredentialVO();
+        credential.setId(1L);
+        credential.setVendor(InstanceVendor.ALIYUN);
+        
when(cloudCredentialRepository.findById(1L)).thenReturn(Optional.of(credential));
+        CloudCatalogProvider catalog = 
org.mockito.Mockito.mock(CloudCatalogProvider.class);
+        CloudInstanceDetailVO detail = new CloudInstanceDetailVO();
+        detail.setInstanceId("rmq-cn-xxx");
+        detail.setInstanceName("prod-mq");
+        detail.setRemark("cloud-remark");
+        detail.setEndpoints(List.of(new 
CloudInstanceDetailVO.CloudEndpoint("TCP_VPC", "vpc:8080")));
+        
when(providerRegistry.catalogFor(InstanceVendor.ALIYUN)).thenReturn(catalog);
+        when(catalog.getCloudInstance(1L, "cn-hangzhou", 
"rmq-cn-xxx")).thenReturn(detail);
+        
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
+
+        InstanceVO created = instanceService.createInstance(instance);
+
+        assertThat(created.getRemark()).isEqualTo("user-remark");
+    }
+
+    @Test
+    void importCloudInstancesShouldRejectApacheVendorTest() {
+        assertThatThrownBy(() -> 
instanceService.importCloudInstances(InstanceVendor.APACHE, 1L))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Import is only supported for cloud vendors")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(400));
+
+        verifyNoInteractions(instanceRepository);
+    }
+
+    @Test
+    void importCloudInstancesShouldImportSkipAndCollectFailuresTest() {
+        CloudCredentialVO credential = new CloudCredentialVO();
+        credential.setId(1L);
+        credential.setVendor(InstanceVendor.ALIYUN);
+        
when(cloudCredentialRepository.findById(1L)).thenReturn(Optional.of(credential));
+
+        CloudCatalogProvider catalog = 
org.mockito.Mockito.mock(CloudCatalogProvider.class);
+        
when(providerRegistry.catalogFor(InstanceVendor.ALIYUN)).thenReturn(catalog);
+
+        CloudRegionVO hangzhou = new CloudRegionVO();
+        hangzhou.setRegionId("cn-hangzhou");
+        CloudRegionVO broken = new CloudRegionVO();
+        broken.setRegionId("cn-broken");
+        when(catalog.listRegions(1L)).thenReturn(List.of(hangzhou, broken));
+
+        CloudInstanceOptionVO fresh = new CloudInstanceOptionVO();
+        fresh.setInstanceId("rmq-fresh");
+        CloudInstanceOptionVO duplicate = new CloudInstanceOptionVO();
+        duplicate.setInstanceId("rmq-dup");
+        when(catalog.listCloudInstances(1L, "cn-hangzhou", 
null)).thenReturn(List.of(fresh, duplicate));
+        when(catalog.listCloudInstances(1L, "cn-broken", null))
+                .thenThrow(new BusinessException(502, "catalog unavailable"));
+
+        CloudInstanceDetailVO freshDetail = new CloudInstanceDetailVO();
+        freshDetail.setInstanceId("rmq-fresh");
+        freshDetail.setInstanceName("inst-fresh");
+        freshDetail.setRemark("cloud-remark");
+        freshDetail.setEndpoints(List.of(new 
CloudInstanceDetailVO.CloudEndpoint("TCP_VPC", "vpc-a:8080")));
+        CloudInstanceDetailVO duplicateDetail = new CloudInstanceDetailVO();
+        duplicateDetail.setInstanceId("rmq-dup");
+        duplicateDetail.setInstanceName("inst-dup");
+        duplicateDetail.setEndpoints(List.of(new 
CloudInstanceDetailVO.CloudEndpoint("TCP_VPC", "vpc-b:8080")));
+        when(catalog.getCloudInstance(1L, "cn-hangzhou", 
"rmq-fresh")).thenReturn(freshDetail);
+        when(catalog.getCloudInstance(1L, "cn-hangzhou", 
"rmq-dup")).thenReturn(duplicateDetail);
+
+        
when(instanceRepository.findByName("rmq-fresh")).thenReturn(Optional.empty());
+        InstanceVO existing = InstanceVO.builder().name("rmq-dup").build();
+        existing.setId(9L);
+        
when(instanceRepository.findByName("rmq-dup")).thenReturn(Optional.of(existing));
+        
when(instanceRepository.save(any(InstanceVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
+
+        CloudImportResultVO result = 
instanceService.importCloudInstances(InstanceVendor.ALIYUN, 1L);
+
+        assertThat(result.getDiscovered()).isEqualTo(2);
+        assertThat(result.getImported()).isEqualTo(1);
+        assertThat(result.getSkipped()).isEqualTo(1);
+        assertThat(result.getFailed()).containsExactly("cn-broken: catalog 
unavailable");
+
+        ArgumentCaptor<InstanceVO> saved = 
ArgumentCaptor.forClass(InstanceVO.class);
+        verify(instanceRepository).save(saved.capture());
+        assertThat(saved.getValue().getName()).isEqualTo("rmq-fresh");
+        assertThat(saved.getValue().getRemark()).isEqualTo("cloud-remark");
+        assertThat(saved.getValue().getType()).isEqualTo(InstanceType.CLOUD);
+        assertThat(saved.getValue().getRegionId()).isEqualTo("cn-hangzhou");
     }
 
     @Test
@@ -1015,9 +1173,9 @@ class InstanceServiceTest {
 
         InstanceVO created = instanceService.createInstance(instance);
 
-        assertThat(created.getName()).isEqualTo("chengdu-prod");
+        assertThat(created.getName()).isEqualTo("rmq-abc");
         assertThat(created.getEndpoint()).isEqualTo("vpc.tencent:8080");
-        assertThat(created.getType()).isEqualTo(InstanceType.PROXY);
+        assertThat(created.getType()).isEqualTo(InstanceType.CLOUD);
         assertThat(created.getVendor()).isEqualTo(InstanceVendor.TENCENT);
     }
 
@@ -1029,7 +1187,7 @@ class InstanceServiceTest {
                 .cloudInstanceId("rmq-cn-xxx")
                 .credentialId(1L)
                 .regionId("cn-hangzhou")
-                .type(InstanceType.PROXY)
+                .type(InstanceType.CLOUD)
                 .endpoint("vpc:8080")
                 .build();
         existing.setId(1L);
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
index ab559c1ee..3aa83c2fa 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/MybatisPlusInstanceRepositoryTest.java
@@ -63,7 +63,7 @@ class MybatisPlusInstanceRepositoryTest {
     void findAllShouldMapEntitiesWithoutCountsTest() {
         when(instanceMapper.selectList(any(QueryWrapper.class)))
                 .thenReturn(List.of(entity(1L, "instance-direct-1", 
InstanceType.DIRECT),
-                        entity(2L, "instance-proxy-1", InstanceType.PROXY)));
+                        entity(2L, "instance-proxy-1", 
InstanceType.PROXY_CLUSTER)));
 
         List<InstanceVO> result = repository.findAll();
 
@@ -81,22 +81,19 @@ class MybatisPlusInstanceRepositoryTest {
     }
 
     @Test
-    void findByTypeShouldTreatLegacyProxyAsAllProxyAccessTypes() {
+    void findByTypeShouldMatchExactTypeNameTest() {
         when(instanceMapper.selectList(any(QueryWrapper.class)))
-                .thenReturn(List.of(
-                        entity(3L, "legacy", InstanceType.PROXY),
-                        entity(4L, "local", InstanceType.PROXY_LOCAL),
-                        entity(5L, "cluster", InstanceType.PROXY_CLUSTER)));
+                .thenReturn(List.of(entity(3L, "cloud-inst", 
InstanceType.CLOUD)));
 
-        List<InstanceVO> result = repository.findByType(InstanceType.PROXY);
+        List<InstanceVO> result = repository.findByType(InstanceType.CLOUD);
 
         assertThat(result).extracting(InstanceVO::getType)
-                .containsExactly(InstanceType.PROXY, InstanceType.PROXY_LOCAL, 
InstanceType.PROXY_CLUSTER);
+                .containsExactly(InstanceType.CLOUD);
         ArgumentCaptor<QueryWrapper<RmqInstance>> query = 
ArgumentCaptor.forClass(QueryWrapper.class);
         verify(instanceMapper).selectList(query.capture());
-        assertThat(query.getValue().getSqlSegment()).contains("type IN");
+        assertThat(query.getValue().getSqlSegment()).contains("type =");
         assertThat(query.getValue().getParamNameValuePairs().values())
-                .contains(InstanceType.PROXY.name(), 
InstanceType.PROXY_LOCAL.name(), InstanceType.PROXY_CLUSTER.name());
+                .containsExactly(InstanceType.CLOUD.name());
     }
 
     @Test
@@ -109,7 +106,7 @@ class MybatisPlusInstanceRepositoryTest {
                 .isEqualTo(InstanceType.PROXY_LOCAL);
         ArgumentCaptor<QueryWrapper<RmqInstance>> query = 
ArgumentCaptor.forClass(QueryWrapper.class);
         verify(instanceMapper).selectList(query.capture());
-        assertThat(query.getValue().getSqlSegment()).contains("type IN");
+        assertThat(query.getValue().getSqlSegment()).contains("type =");
     }
 
     @Test
@@ -137,7 +134,7 @@ class MybatisPlusInstanceRepositoryTest {
 
     @Test
     void findByIdShouldNotComputeCountsTest() {
-        when(instanceMapper.selectById(2L)).thenReturn(entity(2L, 
"instance-proxy-1", InstanceType.PROXY));
+        when(instanceMapper.selectById(2L)).thenReturn(entity(2L, 
"instance-proxy-1", InstanceType.PROXY_CLUSTER));
 
         Optional<InstanceVO> result = repository.findById(2L);
 
@@ -189,7 +186,7 @@ class MybatisPlusInstanceRepositoryTest {
 
     @Test
     void saveShouldInsertWhenInstanceAbsent() {
-        InstanceVO vo = vo(5L, "instance-proxy-2", InstanceType.PROXY);
+        InstanceVO vo = vo(5L, "instance-proxy-2", InstanceType.PROXY_CLUSTER);
         when(instanceMapper.selectById(5L)).thenReturn(null);
 
         repository.save(vo);
@@ -202,8 +199,8 @@ class MybatisPlusInstanceRepositoryTest {
 
     @Test
     void saveShouldUpdateWhenInstanceExists() {
-        InstanceVO vo = vo(5L, "instance-proxy-2", InstanceType.PROXY);
-        when(instanceMapper.selectById(5L)).thenReturn(entity(5L, 
"instance-proxy-2", InstanceType.PROXY));
+        InstanceVO vo = vo(5L, "instance-proxy-2", InstanceType.PROXY_CLUSTER);
+        when(instanceMapper.selectById(5L)).thenReturn(entity(5L, 
"instance-proxy-2", InstanceType.PROXY_CLUSTER));
         when(instanceMapper.updateById(any(RmqInstance.class))).thenReturn(1);
 
         repository.save(vo);
@@ -214,9 +211,9 @@ class MybatisPlusInstanceRepositoryTest {
 
     @Test
     void saveShouldReportALostConcurrentUpdate() {
-        InstanceVO vo = vo(5L, "instance-proxy-2", InstanceType.PROXY);
+        InstanceVO vo = vo(5L, "instance-proxy-2", InstanceType.PROXY_CLUSTER);
         when(instanceMapper.selectById(5L))
-                .thenReturn(entity(5L, "instance-proxy-2", 
InstanceType.PROXY));
+                .thenReturn(entity(5L, "instance-proxy-2", 
InstanceType.PROXY_CLUSTER));
         when(instanceMapper.updateById(any(RmqInstance.class))).thenReturn(0);
 
         assertThatThrownBy(() -> repository.save(vo))
diff --git a/web/src/api/instance.test.ts b/web/src/api/instance.test.ts
index 7998d6dc7..b06d902c3 100644
--- a/web/src/api/instance.test.ts
+++ b/web/src/api/instance.test.ts
@@ -31,7 +31,7 @@ const mock = new MockAdapter(client);
 const instance = {
   id: 1,
   name: 'orders',
-  type: 'PROXY' as const,
+  type: 'PROXY_CLUSTER' as const,
   endpoint: 'proxy:8080',
   remark: '',
   topicCount: 0,
@@ -67,11 +67,11 @@ describe('instance API', () => {
 
   it('sends normalized instance filters', async () => {
     mock.onGet('/instances').reply((config) => {
-      expect(config.params).toEqual({ type: 'PROXY', search: 'proxy:8080' });
+      expect(config.params).toEqual({ type: 'CLOUD', search: 'proxy:8080' });
       return [200, { code: 200, data: [instance] }];
     });
 
-    await expect(listInstances({ type: 'PROXY', search: '  proxy:8080  ' 
})).resolves.toEqual([
+    await expect(listInstances({ type: 'CLOUD', search: '  proxy:8080  ' 
})).resolves.toEqual([
       instance,
     ]);
   });
@@ -98,7 +98,7 @@ describe('instance API', () => {
     const capabilities = {
       instanceId: 'instance/proxy',
       vendor: 'APACHE' as const,
-      accessType: 'PROXY' as const,
+      accessType: 'PROXY_CLUSTER' as const,
       capabilities: ['TOPIC_MANAGEMENT', 'DLQ_MANAGEMENT'] as const,
     };
     mock.onGet('/instances/instance%2Fproxy/capabilities').reply(200, {
diff --git a/web/src/api/instance.ts b/web/src/api/instance.ts
index d61e03c1c..853b923d9 100644
--- a/web/src/api/instance.ts
+++ b/web/src/api/instance.ts
@@ -19,7 +19,7 @@ import client from './client';
 
 // ─── Types ──────────────────────────────────────────────────────
 export type InstanceVendor = 'APACHE' | 'ALIYUN' | 'TENCENT';
-export type InstanceType = 'PROXY' | 'PROXY_LOCAL' | 'PROXY_CLUSTER' | 
'DIRECT';
+export type InstanceType = 'CLOUD' | 'PROXY_LOCAL' | 'PROXY_CLUSTER' | 
'DIRECT';
 export type InstanceCapability =
   | 'TOPIC_MANAGEMENT'
   | 'CONSUMER_GROUP_MANAGEMENT'
@@ -112,6 +112,18 @@ export async function updateInstance(data: 
UpdateInstanceRequest) {
   return res.data.data;
 }
 
+export interface CloudImportResult {
+  discovered: number;
+  imported: number;
+  skipped: number;
+  failed: string[];
+}
+
 export async function deleteInstance(instanceId: string) {
   await client.post('/instances/delete', { id: instanceId });
 }
+
+export async function importCloudInstances(data: { vendor: InstanceVendor; 
credentialId: number }) {
+  const res = await client.post<{ data: CloudImportResult 
}>('/instances/import-cloud', data);
+  return res.data.data;
+}
diff --git a/web/src/hooks/useInstanceFilter.test.tsx 
b/web/src/hooks/useInstanceFilter.test.tsx
index f3722eb8a..461b7f0e1 100644
--- a/web/src/hooks/useInstanceFilter.test.tsx
+++ b/web/src/hooks/useInstanceFilter.test.tsx
@@ -39,7 +39,7 @@ describe('useInstanceFilter', () => {
         id: 7,
         name: 'instance-a',
         remark: '',
-        type: 'PROXY',
+        type: 'PROXY_CLUSTER',
         endpoint: '127.0.0.1:8080',
         topicCount: 0,
         consumerGroupCount: 0,
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index e77f6a69c..648e2ae88 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -179,7 +179,6 @@ const translations: Record<string, Record<Lang, string>> = {
   'instance.count': { zh: '共 {n} 个实例', en: '{n} instances' },
   'instance.searchPlaceholder': { zh: '搜索实例 ID 或地址', en: 'Search instance ID 
or endpoint' },
   'instance.allTypes': { zh: '全部架构', en: 'All Types' },
-  'instance.proxyMode': { zh: 'Proxy 模式', en: 'Proxy Mode' },
   'instance.directMode': { zh: 'Direct 模式', en: 'Direct Mode' },
   'instance.addInstance': { zh: '添加实例', en: 'Add Instance' },
   'instance.instanceName': { zh: '实例 ID', en: 'Instance ID' },
diff --git a/web/src/layouts/MainLayout.test.tsx 
b/web/src/layouts/MainLayout.test.tsx
index 3a9b3b7dc..fbf7844ea 100644
--- a/web/src/layouts/MainLayout.test.tsx
+++ b/web/src/layouts/MainLayout.test.tsx
@@ -175,7 +175,7 @@ describe('MainLayout authentication navigation', () => {
     instanceServiceMocks.getInstanceCapabilities.mockResolvedValue({
       instanceId: 'cloud-1',
       vendor: 'ALIYUN',
-      accessType: 'PROXY',
+      accessType: 'CLOUD',
       capabilities: ['TOPIC_MANAGEMENT'],
     });
 
diff --git a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx 
b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
index 68f908204..d28399a1d 100644
--- a/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
+++ b/web/src/pages/instance/__tests__/ConsumerPage.test.tsx
@@ -112,7 +112,7 @@ describe('Consumer page', () => {
         id: 1,
         name: 'instance-1',
         remark: '',
-        type: 'PROXY',
+        type: 'PROXY_CLUSTER',
         endpoint: '10.0.0.1:8080',
         topicCount: 0,
         consumerGroupCount: 0,
@@ -176,7 +176,7 @@ describe('Consumer page', () => {
         id: 1,
         name: 'instance-1',
         remark: '',
-        type: 'PROXY',
+        type: 'PROXY_CLUSTER',
         endpoint: '10.0.0.1:8080',
         topicCount: 0,
         consumerGroupCount: 0,
@@ -649,7 +649,7 @@ describe('Consumer page', () => {
         id: 4,
         name: 'instance-proxy-1',
         remark: '',
-        type: 'PROXY',
+        type: 'PROXY_CLUSTER',
         endpoint: '10.0.2.21:8080',
         topicCount: 0,
         consumerGroupCount: 0,
diff --git a/web/src/pages/instance/__tests__/InstancePage.test.tsx 
b/web/src/pages/instance/__tests__/InstancePage.test.tsx
index a2f60a103..51ac7a8ad 100644
--- a/web/src/pages/instance/__tests__/InstancePage.test.tsx
+++ b/web/src/pages/instance/__tests__/InstancePage.test.tsx
@@ -45,6 +45,7 @@ vi.mock('../../../api/tencentCatalog', () => ({
 vi.mock('../../../services/instanceService', () => ({
   createInstance: vi.fn(),
   deleteInstance: vi.fn(),
+  importCloudInstances: vi.fn(),
   listInstances: vi.fn(),
   updateInstance: vi.fn(),
 }));
@@ -75,7 +76,7 @@ beforeAll(() => {
 const instance = (
   id: number,
   name: string,
-  type: Instance['type'] = 'PROXY',
+  type: Instance['type'] = 'PROXY_CLUSTER',
   remark: Instance['remark'] = '',
 ): Instance => ({
   id,
@@ -398,6 +399,56 @@ describe('InstancePage', () => {
     expect(within(dialog).getByText(/云凭据与云上实例完成接入/)).toBeInTheDocument();
   });
 
+  it('imports every Aliyun instance of the credential via one-click import', 
async () => {
+    const user = userEvent.setup();
+    vi.mocked(cloudCredentialApi.listCloudCredentials).mockResolvedValue(
+      cloudCredentialPage([
+        {
+          id: 101,
+          name: 'prod-account',
+          vendor: 'ALIYUN',
+          accessKey: 'LTAI-prod',
+          gmtCreate: '2026-01-01T00:00:00Z',
+        },
+      ]),
+    );
+    vi.mocked(instanceService.importCloudInstances).mockResolvedValue({
+      discovered: 3,
+      imported: 2,
+      skipped: 1,
+      failed: [],
+    });
+
+    renderPage();
+    expect(await screen.findByText('production-proxy')).toBeInTheDocument();
+    await user.click(screen.getByRole('button', { name: /添加实例/ }));
+    const dialog = await screen.findByRole('dialog');
+    await user.click(within(dialog).getByRole('tab', { name: /Aliyun 版/ }));
+    await waitFor(() => 
expect(cloudCredentialApi.listCloudCredentials).toHaveBeenCalled());
+
+    const importButton = within(dialog).getByRole('button', { name: /一键导入/ });
+    expect(importButton).toBeDisabled();
+
+    const credentialSelect = within(dialog).getAllByRole('combobox')[0];
+    fireEvent.mouseDown(credentialSelect.parentElement!);
+    await user.click(
+      await screen.findByText(/prod-account/, { selector: 
'.ant-select-item-option-content' }),
+    );
+    await waitFor(() => expect(importButton).toBeEnabled());
+
+    await user.click(importButton);
+
+    await waitFor(() =>
+      expect(instanceService.importCloudInstances).toHaveBeenCalledWith({
+        vendor: 'ALIYUN',
+        credentialId: 101,
+      }),
+    );
+    expect(
+      await screen.findByText(/导入完成:共同步 3 个实例(新导入 2,已存在跳过 1)/),
+    ).toBeInTheDocument();
+  });
+
   it('ignores a stale region response after the cloud credential changes', 
async () => {
     const user = userEvent.setup();
     const oldRegions = deferred<Array<{ regionId: string; regionName: string 
}>>();
@@ -687,8 +738,8 @@ describe('InstancePage', () => {
   it('sorts and renders instances without remarks', async () => {
     const user = userEvent.setup();
     vi.mocked(instanceService.listInstances).mockResolvedValue([
-      instance(10, 'instance-without-remark', 'PROXY', null),
-      instance(11, 'instance-with-remark', 'PROXY', 'production'),
+      instance(10, 'instance-without-remark', 'PROXY_CLUSTER', null),
+      instance(11, 'instance-with-remark', 'PROXY_CLUSTER', 'production'),
     ]);
     renderPage();
 
diff --git a/web/src/pages/instance/__tests__/TopicPage.test.tsx 
b/web/src/pages/instance/__tests__/TopicPage.test.tsx
index 6a61755f1..7c97b2009 100644
--- a/web/src/pages/instance/__tests__/TopicPage.test.tsx
+++ b/web/src/pages/instance/__tests__/TopicPage.test.tsx
@@ -101,7 +101,7 @@ const selectedInstance = {
   id: 5,
   name: 'instance-proxy-1',
   remark: '',
-  type: 'PROXY' as const,
+  type: 'PROXY_CLUSTER' as const,
   endpoint: '10.0.2.21:8080',
   topicCount: 0,
   consumerGroupCount: 0,
@@ -157,7 +157,7 @@ describe('TopicPage', () => {
         id: 5,
         name: 'instance-proxy-1',
         remark: '',
-        type: 'PROXY',
+        type: 'PROXY_CLUSTER',
         endpoint: '10.0.2.21:8080',
         topicCount: 1,
         consumerGroupCount: 0,
@@ -258,9 +258,7 @@ describe('TopicPage', () => {
         gmtModified: '2026-01-01T00:00:00Z',
       },
     ]);
-    mockTopicsList(
-      buildTopics(25).map((topic) => ({ ...topic, instanceId: 'instance-a' })),
-    );
+    mockTopicsList(buildTopics(25).map((topic) => ({ ...topic, instanceId: 
'instance-a' })));
     renderWithProviders('/instance/instance-a/topic');
 
     expect(await screen.findByText('topic-01')).toBeInTheDocument();
@@ -355,7 +353,7 @@ describe('TopicPage', () => {
         id: 5,
         name: 'instance-proxy-1',
         remark: '',
-        type: 'PROXY',
+        type: 'PROXY_CLUSTER',
         endpoint: '10.0.2.21:8080',
         topicCount: 1,
         consumerGroupCount: 0,
@@ -366,7 +364,7 @@ describe('TopicPage', () => {
         id: 7,
         name: 'instance-proxy-2',
         remark: '',
-        type: 'PROXY',
+        type: 'PROXY_CLUSTER',
         endpoint: '10.0.2.22:8080',
         topicCount: 1,
         consumerGroupCount: 0,
@@ -415,9 +413,7 @@ describe('TopicPage', () => {
   it('does not call createTopic when imported topic CSV is invalid or 
duplicated', async () => {
     const user = userEvent.setup();
     instanceServiceMocks.listInstances.mockResolvedValue([selectedInstance]);
-    mockTopicsList([
-      { ...buildTopics(1)[0], instanceId: 'instance-proxy-1' },
-    ]);
+    mockTopicsList([{ ...buildTopics(1)[0], instanceId: 'instance-proxy-1' }]);
     renderWithProviders('/instance/instance-proxy-1/topic');
 
     expect(await screen.findByText('topic-01')).toBeInTheDocument();
diff --git a/web/src/pages/instance/index.tsx b/web/src/pages/instance/index.tsx
index 547f5a61e..0d74e93ff 100644
--- a/web/src/pages/instance/index.tsx
+++ b/web/src/pages/instance/index.tsx
@@ -51,6 +51,7 @@ import { formatDateTime } from '../../utils/format';
 import {
   createInstance,
   deleteInstance,
+  importCloudInstances,
   listInstances,
   updateInstance,
 } from '../../services/instanceService';
@@ -65,7 +66,7 @@ const DEFAULT_CLOUD_REGION_IDS: 
Partial<Record<InstanceVendor, string>> = {
 
 /* ─── Helpers ─── */
 const typeLabel: Record<string, { text: string; color: string }> = {
-  PROXY: { text: 'Proxy 模式(部署形态未标明)', color: 'blue' },
+  CLOUD: { text: '云服务', color: 'blue' },
   PROXY_LOCAL: { text: 'Proxy Local 模式', color: 'cyan' },
   PROXY_CLUSTER: { text: 'Proxy Cluster 模式', color: 'blue' },
   DIRECT: { text: 'Direct 模式', color: 'orange' },
@@ -125,6 +126,7 @@ const InstancePage = () => {
   const [editForm] = Form.useForm();
   const editInstanceType = Form.useWatch<Instance['type'] | undefined>('type', 
editForm);
   const [submitting, setSubmitting] = useState(false);
+  const [importing, setImporting] = useState(false);
   const requestIdRef = useRef(0);
   const mutationInFlightRef = useRef(false);
   const listQueryRef = useRef<InstanceQuery>({});
@@ -339,6 +341,38 @@ const InstancePage = () => {
     }
   };
 
+  const handleImportAll = async () => {
+    if (importing || vendor === 'APACHE') return;
+    const credentialId = addForm.getFieldValue('credentialId') as number | 
undefined;
+    if (!credentialId) {
+      message.warning('请先选择云凭据');
+      return;
+    }
+    setImporting(true);
+    try {
+      const result = await importCloudInstances({ vendor, credentialId });
+      await loadInstances();
+      const summary =
+        result.imported > 0
+          ? `导入完成:共同步 ${result.imported + result.skipped} 个实例(新导入 
${result.imported},已存在跳过 ${result.skipped})`
+          : `云上实例均已在 Studio 中(共 ${result.skipped} 个),无需重复导入`;
+      if (result.failed.length > 0) {
+        message.warning(`${summary},失败 ${result.failed.length} 
个:${result.failed.join(';')}`);
+      } else {
+        message.success(summary);
+      }
+      setAddModalOpen(false);
+      addForm.resetFields();
+      setVendor(DEFAULT_VENDOR);
+      setRegions([]);
+      setCloudInstances([]);
+    } catch (error) {
+      message.error(describeApiError(error, '一键导入失败,请稍后重试'));
+    } finally {
+      setImporting(false);
+    }
+  };
+
   const handleUpdate = async () => {
     if (!editingInstance || mutationInFlightRef.current) return;
     mutationInFlightRef.current = true;
@@ -569,7 +603,7 @@ const InstancePage = () => {
             style={{ width: 140 }}
             options={[
               { value: 'ALL', label: '全部架构' },
-              { value: 'PROXY', label: '全部 Proxy 模式' },
+              { value: 'CLOUD', label: '云服务' },
               { value: 'PROXY_LOCAL', label: 'Proxy Local 模式' },
               { value: 'PROXY_CLUSTER', label: 'Proxy Cluster 模式' },
               { value: 'DIRECT', label: 'Direct 模式' },
@@ -613,11 +647,36 @@ const InstancePage = () => {
           setRegions([]);
           setCloudInstances([]);
         }}
-        onOk={() => void handleCreate()}
-        confirmLoading={submitting}
-        okText="连接"
-        cancelText="取消"
         width={520}
+        footer={
+          <Flex justify="flex-end" gap={8}>
+            {vendor === 'ALIYUN' && (
+              <Tooltip title="遍历该凭据下全部地域,将所有云上实例导入(幂等,已存在的自动跳过),备注自动取自云上实例">
+                <Button
+                  loading={importing}
+                  disabled={!addCredentialId}
+                  onClick={() => void handleImportAll()}
+                >
+                  一键导入
+                </Button>
+              </Tooltip>
+            )}
+            <Button
+              onClick={() => {
+                setAddModalOpen(false);
+                addForm.resetFields();
+                setVendor(DEFAULT_VENDOR);
+                setRegions([]);
+                setCloudInstances([]);
+              }}
+            >
+              取消
+            </Button>
+            <Button type="primary" loading={submitting} onClick={() => void 
handleCreate()}>
+              连接
+            </Button>
+          </Flex>
+        }
       >
         <Tabs
           type="card"
@@ -638,92 +697,94 @@ const InstancePage = () => {
           {VENDOR_OPTIONS.find((option) => option.key === vendor)?.description}
         </Text>
         {cloudVendor ? (
-          <Form form={addForm} layout="vertical">
-            <Form.Item
-              label="云凭据"
-              name="credentialId"
-              rules={[{ required: true, message: '请选择云凭据' }]}
-              extra={
-                <span>
-                  凭据为{vendor === 'ALIYUN' ? '阿里云' : '腾讯云'}账号的 AK/SK,
-                  <Link to="/settings?tab=credential">前往「设置 - 云凭据管理」添加</Link>
-                </span>
-              }
-            >
-              <Select
-                placeholder="选择已录入的 AK/SK 凭据"
-                loading={credentialsLoading}
-                onChange={handleCredentialChange}
-                notFoundContent={
-                  credentialsLoading ? (
-                    '加载中…'
-                  ) : (
-                    <span>
-                      暂无{vendor === 'ALIYUN' ? '阿里云' : '腾讯云'}凭据,
-                      <Link to="/settings?tab=credential">去设置中添加</Link>
-                    </span>
-                  )
+          <>
+            <Form form={addForm} layout="vertical">
+              <Form.Item
+                label="云凭据"
+                name="credentialId"
+                rules={[{ required: true, message: '请选择云凭据' }]}
+                extra={
+                  <span>
+                    凭据为{vendor === 'ALIYUN' ? '阿里云' : '腾讯云'}账号的 AK/SK,
+                    <Link to="/settings?tab=credential">前往「设置 - 云凭据管理」添加</Link>
+                  </span>
                 }
-                options={credentials.map((item) => ({
-                  value: item.id,
-                  label: `${item.name}(${item.accessKey})`,
-                }))}
-              />
-            </Form.Item>
-            <Form.Item
-              label="地域"
-              name="regionId"
-              rules={[{ required: true, message: '请选择地域' }]}
-            >
-              <Select
-                placeholder={addCredentialId ? '选择地域' : '请先选择云凭据'}
-                disabled={!addCredentialId}
-                loading={regionsLoading}
-                onChange={handleRegionChange}
-                options={regions.map((region) => ({
-                  value: region.regionId,
-                  label: `${region.regionName}(${region.regionId})`,
-                }))}
-              />
-            </Form.Item>
-            <Form.Item
-              label="云上实例"
-              name="cloudInstanceId"
-              rules={[{ required: true, message: '请选择云上实例' }]}
-              extra="商业版实例来自云端目录,无法手工创建"
-            >
-              <Select
-                showSearch
-                optionFilterProp="label"
-                placeholder={addRegionId ? '选择云上实例' : '请先选择地域'}
-                disabled={!addRegionId}
-                loading={cloudInstancesLoading}
-                options={cloudInstances.map((item) => ({
-                  value: item.instanceId,
-                  label: `${item.instanceName || 
item.instanceId}(${item.instanceId})`,
-                }))}
-                onChange={(value) => {
-                  const selected = cloudInstances.find((item) => 
item.instanceId === value);
-                  if (selected) {
-                    addForm.setFieldsValue({ name: selected.instanceId });
+              >
+                <Select
+                  placeholder="选择已录入的 AK/SK 凭据"
+                  loading={credentialsLoading}
+                  onChange={handleCredentialChange}
+                  notFoundContent={
+                    credentialsLoading ? (
+                      '加载中…'
+                    ) : (
+                      <span>
+                        暂无{vendor === 'ALIYUN' ? '阿里云' : '腾讯云'}凭据,
+                        <Link to="/settings?tab=credential">去设置中添加</Link>
+                      </span>
+                    )
                   }
-                }}
-              />
-            </Form.Item>
-            <Form.Item
-              label="实例 ID"
-              name="name"
-              rules={[
-                { required: true, message: '请输入实例 ID' },
-                { max: 64, message: '实例 ID 不能超过 64 个字符' },
-              ]}
-            >
-              <Input placeholder="默认取云上实例 ID" />
-            </Form.Item>
-            <Form.Item label="备注" name="remark">
-              <Input.TextArea rows={2} placeholder="可选,描述实例用途" />
-            </Form.Item>
-          </Form>
+                  options={credentials.map((item) => ({
+                    value: item.id,
+                    label: `${item.name}(${item.accessKey})`,
+                  }))}
+                />
+              </Form.Item>
+              <Form.Item
+                label="地域"
+                name="regionId"
+                rules={[{ required: true, message: '请选择地域' }]}
+              >
+                <Select
+                  placeholder={addCredentialId ? '选择地域' : '请先选择云凭据'}
+                  disabled={!addCredentialId}
+                  loading={regionsLoading}
+                  onChange={handleRegionChange}
+                  options={regions.map((region) => ({
+                    value: region.regionId,
+                    label: `${region.regionName}(${region.regionId})`,
+                  }))}
+                />
+              </Form.Item>
+              <Form.Item
+                label="云上实例"
+                name="cloudInstanceId"
+                rules={[{ required: true, message: '请选择云上实例' }]}
+                extra="商业版实例来自云端目录,无法手工创建"
+              >
+                <Select
+                  showSearch
+                  optionFilterProp="label"
+                  placeholder={addRegionId ? '选择云上实例' : '请先选择地域'}
+                  disabled={!addRegionId}
+                  loading={cloudInstancesLoading}
+                  options={cloudInstances.map((item) => ({
+                    value: item.instanceId,
+                    label: `${item.instanceName || 
item.instanceId}(${item.instanceId})`,
+                  }))}
+                  onChange={(value) => {
+                    const selected = cloudInstances.find((item) => 
item.instanceId === value);
+                    if (selected) {
+                      addForm.setFieldsValue({ name: selected.instanceId });
+                    }
+                  }}
+                />
+              </Form.Item>
+              <Form.Item
+                label="实例 ID"
+                name="name"
+                rules={[
+                  { required: true, message: '请输入实例 ID' },
+                  { max: 64, message: '实例 ID 不能超过 64 个字符' },
+                ]}
+              >
+                <Input placeholder="默认取云上实例 ID" />
+              </Form.Item>
+              <Form.Item label="备注" name="remark">
+                <Input.TextArea rows={2} placeholder="可选,描述实例用途" />
+              </Form.Item>
+            </Form>
+          </>
         ) : (
           <Form form={addForm} layout="vertical">
             <Form.Item
@@ -817,12 +878,15 @@ const InstancePage = () => {
             rules={[{ required: true, message: '请选择接入方式' }]}
           >
             <Select
-              options={[
-                { value: 'PROXY', label: 'Proxy 模式(部署形态未标明)' },
-                { value: 'PROXY_LOCAL', label: 'Proxy Local 模式' },
-                { value: 'PROXY_CLUSTER', label: 'Proxy Cluster 模式' },
-                { value: 'DIRECT', label: 'Direct 模式' },
-              ]}
+              options={
+                editingInstance?.vendor && editingInstance.vendor !== 'APACHE'
+                  ? [{ value: 'CLOUD', label: '云服务' }]
+                  : [
+                      { value: 'PROXY_LOCAL', label: 'Proxy Local 模式' },
+                      { value: 'PROXY_CLUSTER', label: 'Proxy Cluster 模式' },
+                      { value: 'DIRECT', label: 'Direct 模式' },
+                    ]
+              }
             />
           </Form.Item>
           <Form.Item
@@ -839,8 +903,8 @@ const InstancePage = () => {
             extra={
               editInstanceType === 'DIRECT'
                 ? 'Direct 模式请填写 NameServer SLB 地址(K8s 场景下一般为 NameServer 
Service 地址,如 namesrv.mq.svc:9876)'
-                : editInstanceType === 'PROXY'
-                  ? 'Proxy 模式请填写 Proxy SLB 内网地址(如 proxy.mq.svc:8080)'
+                : editInstanceType === 'CLOUD'
+                  ? '云服务实例接入地址由云厂商目录解析,不支持手动修改'
                   : '请先选择接入方式'
             }
           >
diff --git a/web/src/pages/instance/topic.tsx b/web/src/pages/instance/topic.tsx
index 49ecfc123..da0cfb4ac 100644
--- a/web/src/pages/instance/topic.tsx
+++ b/web/src/pages/instance/topic.tsx
@@ -82,15 +82,15 @@ import { parseMessageProperties } from 
'../../utils/messageProperties';
 const { Text } = Typography;
 
 const INSTANCE_ACCESS_LABEL: Record<Instance['type'], string> = {
-  PROXY: 'Proxy 模式(部署形态未标明)',
+  CLOUD: '云服务',
   PROXY_LOCAL: 'Proxy Local 模式',
   PROXY_CLUSTER: 'Proxy Cluster 模式',
   DIRECT: 'Direct 模式',
 };
 
 const INSTANCE_ACCESS_DESCRIPTION: Record<Instance['type'], string> = {
-  PROXY:
-    '接入点为 Proxy 地址,但该存量实例未标明 Local/Cluster 部署形态。若客户端环境无法解析该地址,请自行配置 DNS 
解析或在客户端 hosts 中映射。',
+  CLOUD:
+    '接入点为云厂商托管实例的接入地址,由云实例目录解析得出。若客户端环境无法解析该地址,请自行配置 DNS 解析或在客户端 hosts 中映射。',
   PROXY_LOCAL:
     '接入点为与 Broker 同进程部署的 Proxy 地址。若客户端环境无法解析该地址,请自行配置 DNS 解析或在客户端 hosts 中映射。',
   PROXY_CLUSTER:
diff --git a/web/src/services/instanceService.test.ts 
b/web/src/services/instanceService.test.ts
index 49798c903..12e7d2b4a 100644
--- a/web/src/services/instanceService.test.ts
+++ b/web/src/services/instanceService.test.ts
@@ -56,21 +56,18 @@ describe('instanceService mock instances', () => {
     const combined = await listInstances({ type: 'DIRECT', search: 
'instance-direct-2' });
     expect(combined.map((instance) => instance.id)).toEqual([2]);
 
-    const allProxy = await listInstances({ type: 'PROXY' });
-    expect(allProxy.map((instance) => instance.type)).toEqual([
-      'PROXY_CLUSTER',
-      'PROXY_CLUSTER',
-      'PROXY_LOCAL',
-    ]);
+    const clusterOnly = await listInstances({ type: 'PROXY_CLUSTER' });
+    expect(clusterOnly.map((instance) => instance.id)).toEqual([3, 4]);
     await expect(listInstances({ type: 'PROXY_LOCAL' })).resolves.toEqual([
       expect.objectContaining({ type: 'PROXY_LOCAL' }),
     ]);
+    await expect(listInstances({ type: 'CLOUD' })).resolves.toEqual([]);
   });
 
   it('does not expose created or updated store records by reference', async () 
=> {
     const created = await createInstance({
       name: 'rocketmq-copy-test',
-      type: 'PROXY',
+      type: 'PROXY_CLUSTER',
       endpoint: 'proxy-copy-test:8080',
       remark: 'created',
     });
diff --git a/web/src/services/instanceService.ts 
b/web/src/services/instanceService.ts
index bdfff2349..d50676b21 100644
--- a/web/src/services/instanceService.ts
+++ b/web/src/services/instanceService.ts
@@ -4,6 +4,7 @@ import type {
   Instance,
   CreateInstanceRequest,
   InstanceQuery,
+  InstanceVendor,
   UpdateInstanceRequest,
   InstanceCapabilities,
 } from '../api/instance';
@@ -17,7 +18,7 @@ function copyInstance(instance: Instance): Instance {
 
 function matchesType(instance: Instance, type?: Instance['type']) {
   if (!type) return true;
-  return type === 'PROXY' ? instance.type !== 'DIRECT' : instance.type === 
type;
+  return instance.type === type;
 }
 
 const APACHE_CAPABILITIES: InstanceCapabilities['capabilities'] = [
@@ -76,11 +77,7 @@ export async function createInstance(data: 
CreateInstanceRequest): Promise<Insta
       id: Date.now(),
       ...data,
       name: data.name || '',
-      type: cloudManaged
-        ? 'PROXY'
-        : data.type === 'PROXY'
-          ? 'PROXY_CLUSTER'
-          : data.type || 'PROXY_CLUSTER',
+      type: cloudManaged ? 'CLOUD' : data.type || 'PROXY_CLUSTER',
       endpoint: data.endpoint || '',
       vendor: data.vendor || 'APACHE',
       remark: data.remark || '',
@@ -95,6 +92,16 @@ export async function createInstance(data: 
CreateInstanceRequest): Promise<Insta
   return instanceApi.createInstance(data);
 }
 
+export async function importCloudInstances(data: {
+  vendor: Exclude<InstanceVendor, 'APACHE'>;
+  credentialId: number;
+}): Promise<instanceApi.CloudImportResult> {
+  if (isMockMode()) {
+    return { discovered: 0, imported: 0, skipped: 0, failed: [] };
+  }
+  return instanceApi.importCloudInstances(data);
+}
+
 export async function updateInstance(data: UpdateInstanceRequest): 
Promise<Instance> {
   if (isMockMode()) {
     const { instanceId, ...changes } = data;

Reply via email to