feat(connection): 新增生产连接只读保护

This commit is contained in:
Syngnat
2026-06-23 15:33:11 +08:00
parent 3205d131c9
commit b0a9a995fb
30 changed files with 776 additions and 19 deletions

View File

@@ -37,6 +37,7 @@ import {
import ConnectionModalMongoSections from "../ConnectionModalMongoSections";
import ConnectionModalRedisSections from "../ConnectionModalRedisSections";
import { t } from "../../i18n";
import { supportsConnectionReadOnlyMode } from "../../utils/connectionReadOnly";
import {
getConnectionConfigLayoutKindLabel,
getStoredSecretPlaceholder,
@@ -192,6 +193,11 @@ const ConnectionModalStep2: React.FC<ConnectionModalStep2Props> = (props) => {
} = props;
const renderStep2 = () => {
const showConnectionReadOnlyField = supportsConnectionReadOnlyMode({
type: dbType,
driver: form.getFieldValue("driver"),
oceanBaseProtocol,
});
const baseInfoSection = (
<div style={modalInnerSectionStyle}>
<div
@@ -1339,6 +1345,27 @@ const renderStep2 = () => {
),
})}
{showConnectionReadOnlyField &&
renderConfigSectionCard({
sectionKey: "readOnly",
icon: <SafetyCertificateOutlined />,
children: (
<Form.Item
name="readOnly"
label={t("connection.modal.field.readOnly.label")}
help={t("connection.modal.field.readOnly.help")}
valuePropName="checked"
style={{ marginBottom: 0 }}
>
<Checkbox
onChange={() => clearConnectionTestResultForChoice()}
>
{t("connection.modal.field.readOnly.checkbox")}
</Checkbox>
</Form.Item>
),
})}
{isMySQLLike &&
renderConfigSectionCard({
sectionKey: "connectionMode",
@@ -2306,6 +2333,7 @@ const renderStep2 = () => {
keepAliveIntervalMinutes: 240,
uri: "",
connectionParams: "",
readOnly: false,
oceanBaseProtocol: "mysql",
mysqlTopology: "single",
rocketmqTopology: "single",

View File

@@ -97,4 +97,29 @@ describe("connectionModalConfig keepalive", () => {
expect(config.keepAliveEnabled).toBe(false);
expect(config.keepAliveIntervalMinutes).toBe(15);
});
it("persists readOnly only for datasource types that support production guard", async () => {
const sqlConfig = await buildConnectionConfig({
values: {
...buildBaseValues(),
type: "postgres",
readOnly: true,
},
forPersist: true,
translate,
});
const redisConfig = await buildConnectionConfig({
values: {
...buildBaseValues(),
type: "redis",
port: 6379,
readOnly: true,
},
forPersist: true,
translate,
});
expect(sqlConfig.readOnly).toBe(true);
expect(redisConfig.readOnly).toBe(false);
});
});

View File

@@ -1,4 +1,5 @@
import type { ConnectionConfig, SavedConnection } from "../../types";
import { supportsConnectionReadOnlyMode } from "../../utils/connectionReadOnly";
import { resolveConnectionSecretDraft } from "../../utils/connectionSecretDraft";
import {
getConnectionTypeDefaultPort as getDefaultPortByType,
@@ -805,6 +806,12 @@ export const buildConnectionConfig = async ({
password: keepPassword ? mergedValues.password || "" : "",
savePassword: savePassword,
database: mergedValues.database || "",
readOnly:
supportsConnectionReadOnlyMode({
type,
driver: mergedValues.driver,
oceanBaseProtocol: selectedOceanBaseProtocol,
}) && mergedValues.readOnly === true,
useSSL: effectiveUseSSL,
sslMode: effectiveUseSSL ? sslMode : "disable",
sslCAPath: sslCAPath,