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 53382a7f fix: prevent unsupported SSL settings from implying success 
(#1048)
53382a7f is described below

commit 53382a7f627384a6d608c27cc57dbf5516332f28
Author: aias00 <[email protected]>
AuthorDate: Wed Aug 5 23:14:51 2026 -0700

    fix: prevent unsupported SSL settings from implying success (#1048)
---
 web/src/i18n/translations.ts                       |   5 +
 web/src/pages/studio/SslSettings.tsx               | 278 +--------------------
 .../pages/studio/__tests__/SslSettings.test.tsx    | 157 ++----------
 3 files changed, 30 insertions(+), 410 deletions(-)

diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index d15ccb37..488f5d4d 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -1055,6 +1055,11 @@ const translations: Record<string, Record<Lang, string>> 
= {
     zh: '配置 SSL/TLS 设置以实现安全通信,更改后需重启服务器。',
     en: 'Configure SSL/TLS for secure communication. Changes require server 
restart.',
   },
+  'ssl.unavailable': { zh: 'SSL/TLS 配置暂不可用', en: 'SSL/TLS configuration is 
unavailable' },
+  'ssl.unavailableDesc': {
+    zh: 'Studio 尚未提供用于验证、持久化或应用 KeyStore/TrustStore 
配置的服务端接口。为避免产生未生效的安全配置,此页面暂不支持保存或上传。',
+    en: 'Studio does not yet provide a server API to validate, persist, or 
apply KeyStore/TrustStore configuration. Saving and uploading are unavailable 
to avoid implying that security settings are active.',
+  },
   'ssl.enabled': { zh: '启用 SSL/TLS', en: 'Enable SSL/TLS' },
   'ssl.protocol': { zh: 'SSL 协议', en: 'SSL Protocol' },
   'ssl.selectProtocol': { zh: '请选择协议', en: 'Select protocol' },
diff --git a/web/src/pages/studio/SslSettings.tsx 
b/web/src/pages/studio/SslSettings.tsx
index 9ed498e1..43fb03af 100644
--- a/web/src/pages/studio/SslSettings.tsx
+++ b/web/src/pages/studio/SslSettings.tsx
@@ -15,100 +15,12 @@
  * limitations under the License.
  */
 
-import { useState } from 'react';
-import {
-  Card,
-  Form,
-  Input,
-  Button,
-  Switch,
-  Select,
-  Upload,
-  Descriptions,
-  Tag,
-  Space,
-  Alert,
-  Divider,
-  Row,
-  Col,
-  App,
-} from 'antd';
-import { ShieldCheck, Lock, CheckCircle, XCircle, UploadSimple } from 
'@phosphor-icons/react';
+import { Alert, Card, Space } from 'antd';
+import { ShieldCheck } from '@phosphor-icons/react';
 import { useLang } from '../../i18n/LangContext';
 
-// ─── Types ──────────────────────────────────────────────────────
-interface SslConfig {
-  enabled: boolean;
-  protocol: string;
-  keyStoreType: string;
-  keyStorePath: string;
-  keyStorePassword: string;
-  trustStoreType: string;
-  trustStorePath: string;
-  trustStorePassword: string;
-  clientAuth: string;
-  certificateExpiry: string | null;
-  certificateIssuer: string | null;
-}
-
-interface FormValues {
-  enabled: boolean;
-  protocol: string;
-  clientAuth: string;
-  keyStoreType: string;
-  keyStorePath: string;
-  keyStorePassword: string;
-  trustStoreType?: string;
-  trustStorePath?: string;
-  trustStorePassword?: string;
-}
-
-// ─── Component ──────────────────────────────────────────────────
 const SslSettingsPage = () => {
-  const [form] = Form.useForm<FormValues>();
-  const [sslConfig] = useState<SslConfig>({
-    enabled: false,
-    protocol: 'TLSv1.3',
-    keyStoreType: 'JKS',
-    keyStorePath: '',
-    keyStorePassword: '',
-    trustStoreType: 'JKS',
-    trustStorePath: '',
-    trustStorePassword: '',
-    clientAuth: 'none',
-    certificateExpiry: '2025-12-31',
-    certificateIssuer: "Let's Encrypt",
-  });
-  const sslEnabled = Form.useWatch('enabled', form) ?? sslConfig.enabled;
-  const clientAuth = Form.useWatch('clientAuth', form) ?? sslConfig.clientAuth;
   const { t } = useLang();
-  const { message } = App.useApp();
-
-  const handleSave = () => {
-    message.error(t('ssl.saveUnavailable'));
-  };
-
-  const uploadProps = {
-    name: 'certificate',
-    multiple: false,
-    beforeUpload: (file: File) => {
-      const isCert =
-        file.name.endsWith('.pem') ||
-        file.name.endsWith('.crt') ||
-        file.name.endsWith('.jks') ||
-        file.name.endsWith('.p12');
-      if (!isCert) {
-        message.error(t('ssl.invalidCertFormat'));
-        return false;
-      }
-      return false;
-    },
-    onChange: (info: { file: { status?: string } }) => {
-      if (info.file.status === 'removed') {
-        message.info(t('ssl.certRemoved'));
-      }
-    },
-  };
 
   return (
     <div style={{ padding: 0 }}>
@@ -123,191 +35,13 @@ const SslSettingsPage = () => {
         style={{ borderRadius: 8, boxShadow: '0 1px 6px rgba(0,0,0,0.04)' }}
       >
         <Alert
-          message={t('ssl.info')}
-          description={t('ssl.infoDesc')}
-          type="info"
+          message={t('ssl.unavailable')}
+          description={t('ssl.unavailableDesc')}
+          type="warning"
           showIcon
-          style={{ marginBottom: 24 }}
+          data-testid="ssl-settings-unavailable"
         />
-
-        <Form form={form} layout="vertical" onFinish={handleSave} 
initialValues={sslConfig}>
-          <Form.Item name="enabled" label={t('ssl.enabled')} 
valuePropName="checked">
-            <Switch
-              checkedChildren={<CheckCircle size={12} />}
-              unCheckedChildren={<XCircle size={12} />}
-            />
-          </Form.Item>
-
-          {sslEnabled && (
-            <>
-              <Divider />
-
-              <Row gutter={16}>
-                <Col span={12}>
-                  <Form.Item
-                    name="protocol"
-                    label={t('ssl.protocol')}
-                    rules={[{ required: true, message: t('ssl.selectProtocol') 
}]}
-                  >
-                    <Select
-                      options={[
-                        { value: 'TLSv1.3', label: 'TLS 1.3' },
-                        { value: 'TLSv1.2', label: 'TLS 1.2' },
-                        { value: 'TLSv1.1', label: 'TLS 1.1 (Deprecated)' },
-                        { value: 'TLSv1', label: 'TLS 1.0 (Deprecated)' },
-                      ]}
-                    />
-                  </Form.Item>
-                </Col>
-                <Col span={12}>
-                  <Form.Item
-                    name="clientAuth"
-                    label={t('ssl.clientAuth')}
-                    rules={[{ required: true }]}
-                  >
-                    <Select
-                      options={[
-                        { value: 'none', label: t('ssl.none') },
-                        { value: 'want', label: t('ssl.want') },
-                        { value: 'need', label: t('ssl.need') },
-                      ]}
-                    />
-                  </Form.Item>
-                </Col>
-              </Row>
-
-              <Divider orientation="left">{t('ssl.keystoreConfig')}</Divider>
-
-              <Row gutter={16}>
-                <Col span={12}>
-                  <Form.Item
-                    name="keyStoreType"
-                    label={t('ssl.keystoreType')}
-                    rules={[{ required: true }]}
-                  >
-                    <Select
-                      options={[
-                        { value: 'JKS', label: 'JKS' },
-                        { value: 'PKCS12', label: 'PKCS12' },
-                      ]}
-                    />
-                  </Form.Item>
-                </Col>
-                <Col span={12}>
-                  <Form.Item
-                    name="keyStorePath"
-                    label={t('ssl.keystorePath')}
-                    rules={[{ required: true, message: 
t('ssl.keystorePathPlaceholder') }]}
-                  >
-                    <Input placeholder="/path/to/keystore.jks" />
-                  </Form.Item>
-                </Col>
-              </Row>
-
-              <Form.Item
-                name="keyStorePassword"
-                label={t('ssl.keystorePassword')}
-                rules={[{ required: true, message: 
t('ssl.keystorePasswordPlaceholder') }]}
-              >
-                <Input.Password 
placeholder={t('ssl.keystorePasswordPlaceholder')} />
-              </Form.Item>
-
-              <Form.Item label={t('ssl.uploadKeystore')}>
-                <Upload {...uploadProps}>
-                  <Button icon={<UploadSimple size={14} 
/>}>{t('ssl.upload')}</Button>
-                </Upload>
-              </Form.Item>
-
-              {clientAuth !== 'none' && (
-                <>
-                  <Divider 
orientation="left">{t('ssl.truststoreConfig')}</Divider>
-
-                  <Row gutter={16}>
-                    <Col span={12}>
-                      <Form.Item
-                        name="trustStoreType"
-                        label={t('ssl.truststoreType')}
-                        rules={[{ required: true }]}
-                      >
-                        <Select
-                          options={[
-                            { value: 'JKS', label: 'JKS' },
-                            { value: 'PKCS12', label: 'PKCS12' },
-                          ]}
-                        />
-                      </Form.Item>
-                    </Col>
-                    <Col span={12}>
-                      <Form.Item
-                        name="trustStorePath"
-                        label={t('ssl.truststorePath')}
-                        rules={[{ required: true, message: 
t('ssl.truststorePathPlaceholder') }]}
-                      >
-                        <Input placeholder="/path/to/truststore.jks" />
-                      </Form.Item>
-                    </Col>
-                  </Row>
-
-                  <Form.Item
-                    name="trustStorePassword"
-                    label={t('ssl.truststorePassword')}
-                    rules={[{ required: true, message: 
t('ssl.truststorePasswordPlaceholder') }]}
-                  >
-                    <Input.Password 
placeholder={t('ssl.truststorePasswordPlaceholder')} />
-                  </Form.Item>
-
-                  <Form.Item label={t('ssl.uploadTruststore')}>
-                    <Upload {...uploadProps}>
-                      <Button icon={<UploadSimple size={14} 
/>}>{t('ssl.upload')}</Button>
-                    </Upload>
-                  </Form.Item>
-                </>
-              )}
-            </>
-          )}
-
-          <Divider />
-
-          <Form.Item>
-            <Space>
-              <Button type="primary" htmlType="submit">
-                {t('ssl.save')}
-              </Button>
-              <Button onClick={() => 
form.setFieldsValue(sslConfig)}>{t('common.reset')}</Button>
-            </Space>
-          </Form.Item>
-        </Form>
       </Card>
-
-      {sslEnabled && sslConfig.certificateExpiry && (
-        <Card
-          title={
-            <Space>
-              <Lock size={18} style={{ color: '#1677ff' }} />
-              <span>{t('ssl.certInfo')}</span>
-            </Space>
-          }
-          bordered={false}
-          style={{ marginTop: 16, borderRadius: 8, boxShadow: '0 1px 6px 
rgba(0,0,0,0.04)' }}
-        >
-          <Descriptions bordered column={2}>
-            <Descriptions.Item label={t('ssl.issuer')}>
-              <Tag color="blue">{sslConfig.certificateIssuer}</Tag>
-            </Descriptions.Item>
-            <Descriptions.Item label={t('ssl.expiryDate')}>
-              <Tag color="green">{sslConfig.certificateExpiry}</Tag>
-            </Descriptions.Item>
-            <Descriptions.Item label={t('ssl.protocol')}>
-              <Tag>{sslConfig.protocol}</Tag>
-            </Descriptions.Item>
-            <Descriptions.Item label={t('common.status')}>
-              <Tag color="success" icon={<CheckCircle size={12} />}>
-                {t('ssl.active')}
-              </Tag>
-            </Descriptions.Item>
-          </Descriptions>
-        </Card>
-      )}
     </div>
   );
 };
diff --git a/web/src/pages/studio/__tests__/SslSettings.test.tsx 
b/web/src/pages/studio/__tests__/SslSettings.test.tsx
index dac2ac1d..876528f9 100644
--- a/web/src/pages/studio/__tests__/SslSettings.test.tsx
+++ b/web/src/pages/studio/__tests__/SslSettings.test.tsx
@@ -15,156 +15,37 @@
  * limitations under the License.
  */
 
-import { describe, it, expect, vi, beforeAll } from 'vitest';
-import { render, screen, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
+import { describe, expect, it } from 'vitest';
+import { render, screen } from '@testing-library/react';
 import { App } from 'antd';
 import { LangProvider } from '../../../i18n/LangContext';
 import SslSettings from '../SslSettings';
 
-// Mock matchMedia for antd responsive components
-beforeAll(() => {
-  Object.defineProperty(window, 'matchMedia', {
-    writable: true,
-    value: vi.fn().mockImplementation((query: string) => ({
-      matches: false,
-      media: query,
-      onchange: null,
-      addListener: vi.fn(),
-      removeListener: vi.fn(),
-      addEventListener: vi.fn(),
-      removeEventListener: vi.fn(),
-      dispatchEvent: vi.fn(),
-    })),
-  });
-});
-
-// Mock react-router-dom
-vi.mock('react-router-dom', () => ({
-  useNavigate: () => vi.fn(),
-  useParams: () => ({}),
-}));
-
-const renderWithProviders = (ui: React.ReactElement) => {
-  return render(
+const renderWithProviders = () =>
+  render(
     <App>
-      <LangProvider>{ui}</LangProvider>
+      <LangProvider>
+        <SslSettings />
+      </LangProvider>
     </App>,
   );
-};
 
 describe('SslSettings Page', () => {
-  it('should render the page title', () => {
-    renderWithProviders(<SslSettings />);
-    expect(screen.getByText('SSL/TLS 设置')).toBeInTheDocument();
-  });
-
-  it('should render the info alert', () => {
-    renderWithProviders(<SslSettings />);
-    expect(screen.getByText('SSL/TLS 配置')).toBeInTheDocument();
-  });
-
-  it('should render the SSL enable switch label', () => {
-    renderWithProviders(<SslSettings />);
-    expect(screen.getByText('启用 SSL/TLS')).toBeInTheDocument();
-  });
+  it('explains that SSL settings cannot be saved before backend support 
exists', () => {
+    renderWithProviders();
 
-  it('should not show SSL config fields when SSL is disabled', () => {
-    renderWithProviders(<SslSettings />);
-    expect(screen.queryByText('KeyStore 配置')).not.toBeInTheDocument();
+    expect(screen.getByTestId('ssl-settings-unavailable')).toHaveTextContent(
+      'SSL/TLS 配置暂不可用',
+    );
+    expect(screen.getByText(/暂不支持保存或上传/)).toBeInTheDocument();
   });
 
-  it('should show SSL config fields after toggling SSL switch', async () => {
-    const user = userEvent.setup();
-    renderWithProviders(<SslSettings />);
-    const switchEl = screen.getByRole('switch');
-    await user.click(switchEl);
-    // After toggling, SSL config fields should appear (may appear multiple 
times in labels + options)
-    const protocolLabels = screen.getAllByText('SSL 协议');
-    expect(protocolLabels.length).toBeGreaterThan(0);
-    expect(screen.getByText('客户端认证')).toBeInTheDocument();
-    expect(screen.getByText('KeyStore 配置')).toBeInTheDocument();
-  });
-
-  it('should show KeyStore fields after enabling SSL', async () => {
-    const user = userEvent.setup();
-    renderWithProviders(<SslSettings />);
-    const switchEl = screen.getByRole('switch');
-    await user.click(switchEl);
-    expect(screen.getByText('KeyStore 类型')).toBeInTheDocument();
-    expect(screen.getByText('KeyStore 路径')).toBeInTheDocument();
-    expect(screen.getByText('KeyStore 密码')).toBeInTheDocument();
-  });
-
-  it('should restore the saved SSL state when resetting the form', async () => 
{
-    const user = userEvent.setup();
-    renderWithProviders(<SslSettings />);
-    const switchEl = screen.getByRole('switch');
-
-    await user.click(switchEl);
-    expect(switchEl).toBeChecked();
-    expect(screen.getByText('KeyStore 配置')).toBeInTheDocument();
-
-    await user.click(screen.getByRole('button', { name: /重\s*置/ }));
-
-    expect(switchEl).not.toBeChecked();
-    expect(screen.queryByText('KeyStore 配置')).not.toBeInTheDocument();
-  });
-
-  it('should keep form actions available after disabling SSL', async () => {
-    const user = userEvent.setup();
-    renderWithProviders(<SslSettings />);
-    const switchEl = screen.getByRole('switch');
-
-    await user.click(switchEl);
-    await user.click(switchEl);
-
-    expect(switchEl).not.toBeChecked();
-    expect(screen.getByRole('button', { name: /保\s*存/ })).toBeInTheDocument();
-    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 />);
-
-    await user.click(screen.getByRole('switch'));
-    expect(screen.queryByText('TrustStore 配置')).not.toBeInTheDocument();
-
-    await user.click(screen.getByLabelText('客户端认证'));
-    await user.click(await screen.findByText('必需'));
-
-    expect(await screen.findByText('TrustStore 配置')).toBeInTheDocument();
-    expect(screen.getByText('TrustStore 类型')).toBeInTheDocument();
-    expect(screen.getByText('TrustStore 路径')).toBeInTheDocument();
-    expect(screen.getByText('TrustStore 密码')).toBeInTheDocument();
-
-    await user.click(screen.getByLabelText('客户端认证'));
-    await user.click(await screen.findByText('无'));
-    await waitFor(() => expect(screen.queryByText('TrustStore 
配置')).not.toBeInTheDocument());
+  it('does not expose local-only controls that imply TLS is configured', () => 
{
+    renderWithProviders();
 
-    await user.click(screen.getByLabelText('客户端认证'));
-    await user.click(await screen.findByText('可选'));
-    expect(await screen.findByText('TrustStore 配置')).toBeInTheDocument();
+    expect(screen.queryByRole('switch')).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: /保\s*存/ 
})).not.toBeInTheDocument();
+    expect(screen.queryByRole('button', { name: /上\s*传/ 
})).not.toBeInTheDocument();
+    expect(screen.queryByText('证书信息')).not.toBeInTheDocument();
   });
 });

Reply via email to