diff --git a/frontend/src/components/ConnectionModal.i18n.test.tsx b/frontend/src/components/ConnectionModal.i18n.test.tsx index 61104cff..5d90f90e 100644 --- a/frontend/src/components/ConnectionModal.i18n.test.tsx +++ b/frontend/src/components/ConnectionModal.i18n.test.tsx @@ -162,6 +162,8 @@ vi.mock("@ant-design/icons", () => { GatewayOutlined: Icon, SafetyCertificateOutlined: Icon, ThunderboltOutlined: Icon, + DownOutlined: Icon, + RightOutlined: Icon, }; }); @@ -526,7 +528,24 @@ describe("ConnectionModal i18n", () => { findClickableCard(renderer!, sourceLabel).props.onClick(); }); - const pageText = textContent(renderer!.toJSON()); + let pageText = textContent(renderer!.toJSON()); + expect(pageText).toContain(expectations[0]); + expect(pageText).toContain( + language === "zh-CN" ? "未限制" : "No restrictions", + ); + expect(pageText).not.toContain(expectations[1]); + expect(pageText).not.toContain(expectations[2]); + + const protectionToggle = renderer!.root.findAll( + (node) => node.props?.["data-connection-config-section-toggle"] === "readOnly", + )[0]; + expect(protectionToggle.props["aria-expanded"]).toBe(false); + + await act(async () => { + protectionToggle.props.onClick({ stopPropagation: vi.fn() }); + }); + + pageText = textContent(renderer!.toJSON()); expectations.forEach((expected) => { expect(pageText).toContain(expected); }); diff --git a/frontend/src/components/ConnectionModal.tsx b/frontend/src/components/ConnectionModal.tsx index 6503f4f3..33dcb6ed 100644 --- a/frontend/src/components/ConnectionModal.tsx +++ b/frontend/src/components/ConnectionModal.tsx @@ -34,6 +34,8 @@ import { GatewayOutlined, SafetyCertificateOutlined, ThunderboltOutlined, + DownOutlined, + RightOutlined, } from "@ant-design/icons"; import { getDbIcon, @@ -693,14 +695,21 @@ const ConnectionModal: React.FC<{ title: string, description: string, badge?: React.ReactNode, + options?: { + cursor?: React.CSSProperties["cursor"]; + marginBottom?: number; + onClick?: () => void; + }, ) => (
@@ -758,20 +767,65 @@ const ConnectionModal: React.FC<{ icon, children, badge, + collapsible, + expanded = true, + onToggle, }: { sectionKey: ConnectionConfigSectionKey; icon: React.ReactNode; children: React.ReactNode; badge?: React.ReactNode; + collapsible?: boolean; + expanded?: boolean; + onToggle?: () => void; }) => { const copy = getConnectionConfigSectionCopy(sectionKey); + const showChildren = !collapsible || expanded; + const resolvedBadge = collapsible ? ( + + {badge} + + + ) : badge; return (
- {renderJvmSectionHeader(icon, copy.title, copy.description, badge)} - {children} + {renderJvmSectionHeader( + icon, + copy.title, + copy.description, + resolvedBadge, + collapsible + ? { + cursor: "pointer", + marginBottom: showChildren ? 14 : 0, + onClick: onToggle, + } + : undefined, + )} + {showChildren ? children : null}
); }; diff --git a/frontend/src/components/connectionModal/ConnectionModalStep2.tsx b/frontend/src/components/connectionModal/ConnectionModalStep2.tsx index 33b56ff2..171c2354 100644 --- a/frontend/src/components/connectionModal/ConnectionModalStep2.tsx +++ b/frontend/src/components/connectionModal/ConnectionModalStep2.tsx @@ -194,7 +194,14 @@ const ConnectionModalStep2: React.FC = (props) => { useSSL, } = props; -const renderStep2 = () => { + const [readOnlyProtectionExpanded, setReadOnlyProtectionExpanded] = + React.useState(false); + + React.useEffect(() => { + setReadOnlyProtectionExpanded(false); + }, [dbType]); + + const renderStep2 = () => { const showConnectionReadOnlyField = supportsConnectionReadOnlyMode({ type: dbType, driver: form.getFieldValue("driver"), @@ -1364,6 +1371,10 @@ const renderStep2 = () => { renderConfigSectionCard({ sectionKey: "readOnly", icon: , + collapsible: true, + expanded: readOnlyProtectionExpanded, + onToggle: () => + setReadOnlyProtectionExpanded((expanded) => !expanded), badge: (