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 c4eae142 fix: remove simulated operational write success (#938)
c4eae142 is described below

commit c4eae1426a952747703d882f2e60c0aa45c8afb2
Author: aias00 <[email protected]>
AuthorDate: Tue Aug 4 03:05:40 2026 -0700

    fix: remove simulated operational write success (#938)
    
    * fix: disable simulated message resend action
    
    * [ISSUE #788] Return unavailable for Ops setting writes
    
    * fix(web): stop simulating SSL settings saves
    
    * fix: stop fake consume verification success
---
 .../org/apache/rocketmq/studio/ops/OpsService.java | 39 ++++++--------
 .../rocketmq/studio/ops/OpsControllerTest.java     | 18 +++++++
 .../apache/rocketmq/studio/ops/OpsServiceTest.java | 61 ++++++++--------------
 web/src/i18n/translations.ts                       |  4 ++
 .../pages/instance/__tests__/MessagePage.test.tsx  | 31 +++++++++++
 .../__tests__/MessagePageAsyncState.test.tsx       | 15 ++++++
 web/src/pages/instance/message.tsx                 | 15 ++++--
 web/src/pages/studio/SslSettings.tsx               | 14 ++---
 .../pages/studio/__tests__/SslSettings.test.tsx    | 20 +++++++
 9 files changed, 140 insertions(+), 77 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/OpsService.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/OpsService.java
index 57ddd095..e21408d3 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/ops/OpsService.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/ops/OpsService.java
@@ -30,6 +30,9 @@ import java.util.Set;
 @Service
 public class OpsService {
 
+    private static final String OPS_SETTINGS_UNAVAILABLE =
+            "Ops settings are not connected to the cluster admin 
configuration";
+
     private final Set<String> namesrvAddrs = new 
LinkedHashSet<>(List.of("127.0.0.1:9876"));
     private String currentNamesrv = "127.0.0.1:9876";
     private boolean useVIPChannel = true;
@@ -45,41 +48,26 @@ public class OpsService {
     }
 
     public synchronized void updateNameServer(String namesrvAddr) {
-        String normalized = normalizeNameServer(namesrvAddr);
-        namesrvAddrs.add(normalized);
-        currentNamesrv = normalized;
-        log.info("Updated current NameServer address to {}", normalized);
+        normalizeNameServer(namesrvAddr);
+        throw settingsUnavailable();
     }
 
     public synchronized void addNameServer(String namesrvAddr) {
-        String normalized = normalizeNameServer(namesrvAddr);
-        namesrvAddrs.add(normalized);
-        log.info("Added NameServer address {}", normalized);
+        normalizeNameServer(namesrvAddr);
+        throw settingsUnavailable();
     }
 
     public synchronized void deleteNameServer(String namesrvAddr) {
-        String normalized = normalizeNameServer(namesrvAddr);
-        if (!namesrvAddrs.contains(normalized)) {
-            throw new BusinessException(404, "NameServer address not found: " 
+ normalized);
-        }
-        if (namesrvAddrs.size() == 1) {
-            throw new BusinessException(409, "Cannot delete the last 
NameServer address");
-        }
-        if (normalized.equals(currentNamesrv)) {
-            throw new BusinessException(409, "Cannot delete the current 
NameServer address");
-        }
-        namesrvAddrs.remove(normalized);
-        log.info("Deleted NameServer address {}", normalized);
+        normalizeNameServer(namesrvAddr);
+        throw settingsUnavailable();
     }
 
     public synchronized void updateVipChannel(boolean enabled) {
-        useVIPChannel = enabled;
-        log.info("Updated VIP channel setting to {}", enabled);
+        throw settingsUnavailable();
     }
 
     public synchronized void updateUseTLS(boolean enabled) {
-        useTLS = enabled;
-        log.info("Updated TLS setting to {}", enabled);
+        throw settingsUnavailable();
     }
 
     private String normalizeNameServer(String namesrvAddr) {
@@ -88,4 +76,9 @@ public class OpsService {
         }
         return namesrvAddr.trim();
     }
+
+    private BusinessException settingsUnavailable() {
+        log.warn(OPS_SETTINGS_UNAVAILABLE);
+        return new BusinessException(501, OPS_SETTINGS_UNAVAILABLE);
+    }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/OpsControllerTest.java 
b/server/src/test/java/org/apache/rocketmq/studio/ops/OpsControllerTest.java
index b8e2ee96..7ea52110 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/ops/OpsControllerTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/ops/OpsControllerTest.java
@@ -18,6 +18,7 @@
 package org.apache.rocketmq.studio.ops;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import 
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -30,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
@@ -81,6 +83,22 @@ class OpsControllerTest {
         verify(opsService).updateNameServer(eq("10.0.0.1:9876"));
     }
 
+    @Test
+    void updateNameSvrAddrShouldReturnUnavailableWhenServiceRejects() throws 
Exception {
+        doThrow(new BusinessException(501, "Ops settings are not connected to 
the cluster admin configuration"))
+                .when(opsService).updateNameServer("10.0.0.1:9876");
+
+        mockMvc.perform(post("/api/ops/updateNameSvrAddr")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        
.content(objectMapper.writeValueAsString(Map.of("namesrvAddr", 
"10.0.0.1:9876"))))
+                .andExpect(status().isNotImplemented())
+                .andExpect(jsonPath("$.code").value(501))
+                .andExpect(jsonPath("$.message").value(
+                        "Ops settings are not connected to the cluster admin 
configuration"));
+
+        verify(opsService).updateNameServer(eq("10.0.0.1:9876"));
+    }
+
     @Test
     void updateNameSvrAddrShouldRejectMissingAddress() throws Exception {
         mockMvc.perform(post("/api/ops/updateNameSvrAddr")
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/OpsServiceTest.java 
b/server/src/test/java/org/apache/rocketmq/studio/ops/OpsServiceTest.java
index 15e24667..1035a8e7 100644
--- a/server/src/test/java/org/apache/rocketmq/studio/ops/OpsServiceTest.java
+++ b/server/src/test/java/org/apache/rocketmq/studio/ops/OpsServiceTest.java
@@ -38,22 +38,19 @@ class OpsServiceTest {
     }
 
     @Test
-    void addAndUpdateNameServerShouldMaintainUniqueAddressList() {
-        opsService.addNameServer(" 10.0.0.1:9876 ");
-        opsService.addNameServer("10.0.0.1:9876");
-        opsService.updateNameServer("10.0.0.2:9876");
-
-        OpsHomeVO home = opsService.getHomePage();
-        assertThat(home.getNamesvrAddrList())
-                .containsExactly("127.0.0.1:9876", "10.0.0.1:9876", 
"10.0.0.2:9876");
-        assertThat(home.getCurrentNamesrv()).isEqualTo("10.0.0.2:9876");
-    }
-
-    @Test
-    void deleteNameServerShouldRemoveNonCurrentAddress() {
-        opsService.addNameServer("10.0.0.1:9876");
-
-        opsService.deleteNameServer(" 10.0.0.1:9876 ");
+    void nameServerWritesShouldReturnUnavailable() {
+        assertThatThrownBy(() -> opsService.addNameServer(" 10.0.0.1:9876 "))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Ops settings are not connected to the cluster 
admin configuration")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(501));
+        assertThatThrownBy(() -> opsService.updateNameServer("10.0.0.2:9876"))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Ops settings are not connected to the cluster 
admin configuration")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(501));
+        assertThatThrownBy(() -> opsService.deleteNameServer("127.0.0.1:9876"))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Ops settings are not connected to the cluster 
admin configuration")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(501));
 
         OpsHomeVO home = opsService.getHomePage();
         
assertThat(home.getNamesvrAddrList()).containsExactly("127.0.0.1:9876");
@@ -61,33 +58,19 @@ class OpsServiceTest {
     }
 
     @Test
-    void deleteNameServerShouldRejectUnknownCurrentAndLastAddress() {
-        assertThatThrownBy(() -> opsService.deleteNameServer("10.0.0.1:9876"))
+    void togglesShouldReturnUnavailable() {
+        assertThatThrownBy(() -> opsService.updateVipChannel(false))
                 .isInstanceOf(BusinessException.class)
-                .hasMessage("NameServer address not found: 10.0.0.1:9876")
-                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(404));
-
-        assertThatThrownBy(() -> opsService.deleteNameServer("127.0.0.1:9876"))
+                .hasMessage("Ops settings are not connected to the cluster 
admin configuration")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(501));
+        assertThatThrownBy(() -> opsService.updateUseTLS(true))
                 .isInstanceOf(BusinessException.class)
-                .hasMessage("Cannot delete the last NameServer address")
-                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(409));
-
-        opsService.addNameServer("10.0.0.1:9876");
-        opsService.updateNameServer("10.0.0.1:9876");
-        assertThatThrownBy(() -> opsService.deleteNameServer("10.0.0.1:9876"))
-                .isInstanceOf(BusinessException.class)
-                .hasMessage("Cannot delete the current NameServer address")
-                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(409));
-    }
-
-    @Test
-    void togglesShouldUpdateHomePageSettings() {
-        opsService.updateVipChannel(false);
-        opsService.updateUseTLS(true);
+                .hasMessage("Ops settings are not connected to the cluster 
admin configuration")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(501));
 
         OpsHomeVO home = opsService.getHomePage();
-        assertThat(home.isUseVIPChannel()).isFalse();
-        assertThat(home.isUseTLS()).isTrue();
+        assertThat(home.isUseVIPChannel()).isTrue();
+        assertThat(home.isUseTLS()).isFalse();
     }
 
     @Test
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index 3838f340..fd7f40c8 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -1076,6 +1076,10 @@ const translations: Record<string, Record<Lang, string>> 
= {
   'ssl.expiryDate': { zh: '过期日期', en: 'Expiry Date' },
   'ssl.active': { zh: '有效', en: 'Active' },
   'ssl.saveSuccess': { zh: 'SSL 配置保存成功', en: 'SSL configuration saved 
successfully' },
+  'ssl.saveUnavailable': {
+    zh: 'SSL 配置保存功能尚未接入真实后端接口',
+    en: 'SSL configuration persistence is not wired to a backend API yet',
+  },
   'ssl.invalidCertFormat': { zh: '仅允许证书文件!', en: 'Only certificate files are 
allowed!' },
   'ssl.certRemoved': { zh: '证书文件已移除', en: 'Certificate file removed' },
 
diff --git a/web/src/pages/instance/__tests__/MessagePage.test.tsx 
b/web/src/pages/instance/__tests__/MessagePage.test.tsx
index 630ae17b..290a8116 100644
--- a/web/src/pages/instance/__tests__/MessagePage.test.tsx
+++ b/web/src/pages/instance/__tests__/MessagePage.test.tsx
@@ -41,6 +41,19 @@ vi.mock('../../../services/topicService', () => ({
 
 import MessagePage from '../message';
 
+const createMessage = (msgId: string) => ({
+  msgId,
+  topic: `topic-${msgId}`,
+  tag: 'tag',
+  key: `key-${msgId}`,
+  body: '{}',
+  storeTime: '2026-07-31T00:00:00Z',
+  bornHost: '127.0.0.1:1000',
+  storeHost: '127.0.0.1:10911',
+  properties: {},
+  size: 2,
+});
+
 beforeAll(() => {
   Object.defineProperty(window, 'matchMedia', {
     writable: true,
@@ -222,4 +235,22 @@ describe('Message page query history', () => {
     expect(screen.queryByText(/invalid/)).not.toBeInTheDocument();
     expect(screen.queryByText(/order-create/)).not.toBeInTheDocument();
   });
+
+  it('does not report consume verification success without a backend API', 
async () => {
+    const user = userEvent.setup();
+    
messageServiceMocks.queryMessages.mockResolvedValue([createMessage('MID-CONSUME-VERIFY-001')]);
+    renderWithProviders(<MessagePage />);
+
+    await user.click(screen.getByText('按 Message ID'));
+    await user.type(screen.getByPlaceholderText('输入 Message ID'), 
'MID-CONSUME-VERIFY-001');
+    await user.click(screen.getByRole('button', { name: /^search查询$/ }));
+
+    expect(await 
screen.findByText('MID-CONSUME-VERIFY-001')).toBeInTheDocument();
+    await user.click(screen.getByRole('button', { name: /验证/ }));
+
+    expect(
+      await screen.findByText('消费验证接口尚未接入,无法确认该消息的真实消费状态'),
+    ).toBeInTheDocument();
+    expect(screen.queryByText(/消费验证成功/)).not.toBeInTheDocument();
+  });
 });
diff --git a/web/src/pages/instance/__tests__/MessagePageAsyncState.test.tsx 
b/web/src/pages/instance/__tests__/MessagePageAsyncState.test.tsx
index 2f938b4b..a2ebe31a 100644
--- a/web/src/pages/instance/__tests__/MessagePageAsyncState.test.tsx
+++ b/web/src/pages/instance/__tests__/MessagePageAsyncState.test.tsx
@@ -131,6 +131,21 @@ describe('MessagePage async request ownership', () => {
     expect(screen.queryByText('late-after-reset')).not.toBeInTheDocument();
   });
 
+  it('keeps normal message resend disabled until a real API is wired', async 
() => {
+    serviceMocks.queryMessages.mockResolvedValue([createMessage('message-a')]);
+    const user = userEvent.setup();
+    renderPage();
+
+    await user.click(screen.getByRole('button', { name: /^search查询$/ }));
+    const row = await screen.findByRole('row', { name: /message-a/ });
+    await user.click(within(row).getByRole('button', { name: /详情/ }));
+
+    const dialog = await screen.findByRole('dialog', { name: '消息详情' });
+    const resendButton = within(dialog).getByRole('button', { name: /重新发送/ });
+    expect(resendButton).toBeDisabled();
+    expect(resendButton).toHaveAttribute('title', '当前版本尚未接入普通消息重新发送接口');
+  });
+
   it('keeps the latest query loading and ignores an earlier query result', 
async () => {
     const firstQuery = createDeferred<MessageRecord[]>();
     const secondQuery = createDeferred<MessageRecord[]>();
diff --git a/web/src/pages/instance/message.tsx 
b/web/src/pages/instance/message.tsx
index e200fd9f..ee56b2b6 100644
--- a/web/src/pages/instance/message.tsx
+++ b/web/src/pages/instance/message.tsx
@@ -72,6 +72,7 @@ type RecentQuery = {
 
 const QUERY_HISTORY_STORAGE_KEY = 'rocketmq-studio-message-query-history';
 const MAX_QUERY_HISTORY = 5;
+const RESEND_UNAVAILABLE_MESSAGE = '当前版本尚未接入普通消息重新发送接口';
 
 const QUERY_OPTIONS = [
   { value: 'topic' as const, label: '按 Topic 查询' },
@@ -348,10 +349,9 @@ const MessagePage = () => {
     if (recentQuery) replayRecentQuery(recentQuery);
   };
 
-  const handleResend = () => {
-    message.success('消息重新发送成功(模拟)');
+  const handleVerifyConsume = () => {
+    message.warning('消费验证接口尚未接入,无法确认该消息的真实消费状态');
   };
-
   const openDetail = async (record: MessageRecord, tab = 'content') => {
     const requestGeneration = traceGenerationRef.current + 1;
     traceGenerationRef.current = requestGeneration;
@@ -475,7 +475,7 @@ const MessagePage = () => {
             size="small"
             icon={<CheckCircleOutlined />}
             style={{ borderColor: '#52c41a', color: '#52c41a' }}
-            onClick={() => message.success(`消息 ${record.msgId.slice(0, 16)}... 
消费验证成功`)}
+            onClick={handleVerifyConsume}
           >
             验证
           </Button>
@@ -774,7 +774,12 @@ const MessagePage = () => {
         footer={
           <Flex justify="flex-end" gap={8}>
             <Button onClick={closeDetail}>关闭</Button>
-            <Button type="primary" icon={<SendOutlined />} 
onClick={handleResend}>
+            <Button
+              type="primary"
+              icon={<SendOutlined />}
+              disabled
+              title={RESEND_UNAVAILABLE_MESSAGE}
+            >
               重新发送
             </Button>
           </Flex>
diff --git a/web/src/pages/studio/SslSettings.tsx 
b/web/src/pages/studio/SslSettings.tsx
index 26b77a64..9ed498e1 100644
--- a/web/src/pages/studio/SslSettings.tsx
+++ b/web/src/pages/studio/SslSettings.tsx
@@ -66,8 +66,7 @@ interface FormValues {
 // ─── Component ──────────────────────────────────────────────────
 const SslSettingsPage = () => {
   const [form] = Form.useForm<FormValues>();
-  const [loading, setLoading] = useState(false);
-  const [sslConfig, setSslConfig] = useState<SslConfig>({
+  const [sslConfig] = useState<SslConfig>({
     enabled: false,
     protocol: 'TLSv1.3',
     keyStoreType: 'JKS',
@@ -85,13 +84,8 @@ const SslSettingsPage = () => {
   const { t } = useLang();
   const { message } = App.useApp();
 
-  const handleSave = (values: FormValues) => {
-    setLoading(true);
-    setTimeout(() => {
-      setLoading(false);
-      message.success(t('ssl.saveSuccess'));
-      setSslConfig({ ...sslConfig, ...values });
-    }, 1000);
+  const handleSave = () => {
+    message.error(t('ssl.saveUnavailable'));
   };
 
   const uploadProps = {
@@ -276,7 +270,7 @@ const SslSettingsPage = () => {
 
           <Form.Item>
             <Space>
-              <Button type="primary" htmlType="submit" loading={loading}>
+              <Button type="primary" htmlType="submit">
                 {t('ssl.save')}
               </Button>
               <Button onClick={() => 
form.setFieldsValue(sslConfig)}>{t('common.reset')}</Button>
diff --git a/web/src/pages/studio/__tests__/SslSettings.test.tsx 
b/web/src/pages/studio/__tests__/SslSettings.test.tsx
index a921c7e8..dac2ac1d 100644
--- a/web/src/pages/studio/__tests__/SslSettings.test.tsx
+++ b/web/src/pages/studio/__tests__/SslSettings.test.tsx
@@ -124,6 +124,26 @@ describe('SslSettings Page', () => {
     expect(screen.getByRole('button', { name: /重\s*置/ })).toBeInTheDocument();
   });
 
+  it('does not persist SSL changes when the backend API is unavailable', async 
() => {
+    const user = userEvent.setup();
+    renderWithProviders(<SslSettings />);
+
+    const switchEl = screen.getByRole('switch');
+    await user.click(switchEl);
+    await user.type(screen.getByLabelText('KeyStore 路径'), 
'/etc/rocketmq/keystore.jks');
+    await user.type(screen.getByLabelText('KeyStore 密码'), 'changeit');
+
+    await user.click(screen.getByRole('button', { name: /保\s*存/ }));
+
+    expect(await screen.findByText('SSL 
配置保存功能尚未接入真实后端接口')).toBeInTheDocument();
+    expect(screen.queryByText('SSL 配置保存成功')).not.toBeInTheDocument();
+
+    await user.click(screen.getByRole('button', { name: /重\s*置/ }));
+
+    expect(switchEl).not.toBeChecked();
+    expect(screen.queryByText('KeyStore 配置')).not.toBeInTheDocument();
+  });
+
   it('should show TrustStore fields when client authentication is required', 
async () => {
     const user = userEvent.setup();
     renderWithProviders(<SslSettings />);

Reply via email to