From 700c4bf8d106f0d48cb76af6b42a5050d32b3b1a Mon Sep 17 00:00:00 2001 From: Cheese Date: Fri, 12 May 2023 02:39:00 +0000 Subject: [PATCH] feat: tidb cloud serverless tier support (#85) * feat: tidb cloud serverless tier support * clearify: check the engine type before call the tidb connection change function * delete: unused type declare --- package.json | 1 + pnpm-lock.yaml | 73 ++++++-- public/tidb-cloud.svg | 6 + src/components/CreateConnectionModal.tsx | 210 ++++++++++++----------- src/components/EngineIcon.tsx | 2 + src/components/Icon.tsx | 7 + src/locales/en.json | 3 +- src/locales/es.json | 3 +- src/locales/zh.json | 3 +- src/pages/api/connection/db.ts | 8 +- src/pages/api/connection/db_schema.ts | 8 +- src/pages/api/connection/execute.ts | 7 +- src/pages/api/connection/test.ts | 7 +- src/types/connection.ts | 3 + src/utils/index.ts | 1 + src/utils/tidb.ts | 16 ++ 16 files changed, 240 insertions(+), 118 deletions(-) create mode 100644 public/tidb-cloud.svg create mode 100644 src/utils/tidb.ts diff --git a/package.json b/package.json index eb41d9a..b9b60c9 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "react-is": "^18.2.0", "react-loader-spinner": "^5.3.4", "react-markdown": "^8.0.6", + "react-svg": "^16.1.11", "react-textarea-autosize": "^8.4.0", "react-use": "^17.4.0", "remark-gfm": "^3.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5e2725..91a57c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,9 @@ dependencies: react-markdown: specifier: ^8.0.6 version: 8.0.6(@types/react@18.0.28)(react@18.2.0) + react-svg: + specifier: ^16.1.11 + version: 16.1.11(react-dom@18.2.0)(react@18.2.0) react-textarea-autosize: specifier: ^8.4.0 version: 8.4.0(@types/react@18.0.28)(react@18.2.0) @@ -476,6 +479,13 @@ packages: dependencies: regenerator-runtime: 0.13.11 + /@babel/runtime@7.21.5: + resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: false + /@babel/template@7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} @@ -648,7 +658,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 espree: 9.5.0 globals: 13.20.0 ignore: 5.2.4 @@ -709,7 +719,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -1577,6 +1587,14 @@ packages: tailwindcss: 3.2.7(postcss@8.4.21)(ts-node@10.9.1) dev: true + /@tanem/svg-injector@10.1.53: + resolution: {integrity: sha512-tR2Kh0GcTk+7hFTUsCo7JcEsAxz7j28dvaeC77jDp+acgGVdWXtk1v6lY8G0v1g813sgBrBzUr3St8RPBSmjEQ==} + dependencies: + '@babel/runtime': 7.21.5 + content-type: 1.0.5 + tslib: 2.5.0 + dev: false + /@tediousjs/connection-string@0.4.2: resolution: {integrity: sha512-1R9UC7Qc5wief2oJL+c1+d7v1/oPBayL85u8L/jV2DzIKput1TZ8ZUjj2nxQaSfzu210zp0oFWUrYUiUs8NhBQ==} dev: true @@ -1745,7 +1763,7 @@ packages: '@typescript-eslint/scope-manager': 5.55.0 '@typescript-eslint/types': 5.55.0 '@typescript-eslint/typescript-estree': 5.55.0(typescript@4.9.5) - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 eslint: 8.20.0 typescript: 4.9.5 transitivePeerDependencies: @@ -1776,7 +1794,7 @@ packages: dependencies: '@typescript-eslint/types': 5.55.0 '@typescript-eslint/visitor-keys': 5.55.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.8 @@ -1848,7 +1866,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color dev: true @@ -2249,6 +2267,11 @@ packages: engines: {node: '>= 0.6'} dev: false + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: false @@ -2343,6 +2366,17 @@ packages: ms: 2.1.3 dev: true + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + /debug@4.3.4(supports-color@5.5.0): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -2354,6 +2388,7 @@ packages: dependencies: ms: 2.1.2 supports-color: 5.5.0 + dev: false /decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} @@ -2666,7 +2701,7 @@ packages: eslint: '*' eslint-plugin-import: '*' dependencies: - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 eslint: 8.20.0 eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.55.0)(eslint-import-resolver-typescript@2.7.1)(eslint@8.20.0) glob: 7.2.3 @@ -2836,7 +2871,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 @@ -3173,6 +3208,7 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} + dev: false /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -3263,7 +3299,7 @@ packages: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color dev: true @@ -3273,7 +3309,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color dev: true @@ -4138,7 +4174,7 @@ packages: resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==} dependencies: '@types/debug': 4.1.7 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.0.6 micromark-factory-space: 1.0.0 @@ -4205,7 +4241,7 @@ packages: dependencies: '@tediousjs/connection-string': 0.4.2 commander: 9.5.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 rfdc: 1.3.0 tarn: 3.0.2 tedious: 15.1.3 @@ -4965,6 +5001,20 @@ packages: tslib: 2.5.0 dev: false + /react-svg@16.1.11(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Utrm1NATp/1PVML4g97yrFyLvbKZ9HhzFydSsUPuCd8eEYf8llj8sBaYgLO1d5UX5sJ9j2sLgSbOLrGGRhd8NQ==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.21.5 + '@tanem/svg-injector': 10.1.53 + '@types/prop-types': 15.7.5 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-syntax-highlighter@15.5.0(react@18.2.0): resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} peerDependencies: @@ -5483,6 +5533,7 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 + dev: false /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} diff --git a/public/tidb-cloud.svg b/public/tidb-cloud.svg new file mode 100644 index 0000000..556841f --- /dev/null +++ b/public/tidb-cloud.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/components/CreateConnectionModal.tsx b/src/components/CreateConnectionModal.tsx index 03608d6..7cb75c1 100644 --- a/src/components/CreateConnectionModal.tsx +++ b/src/components/CreateConnectionModal.tsx @@ -231,6 +231,7 @@ const CreateConnectionModal = (props: Props) => { { value: Engine.MySQL, label: "MySQL" }, { value: Engine.PostgreSQL, label: "PostgreSQL" }, { value: Engine.MSSQL, label: "MSSQL" }, + { value: Engine.TiDBServerless, label: "TiDB Serverless Tier" }, ]} onValueChange={(value) => setPartialConnection({ engineType: value as Engine }) @@ -300,114 +301,123 @@ const CreateConnectionModal = (props: Props) => { onChange={(value) => setPartialConnection({ password: value })} /> -
- -
- {SSLTypeOptions.map((option) => ( - - ))} + {connection.engineType === Engine.TiDBServerless ? ( +
+
- {sslType !== "none" && ( - <> -
- setSelectedSSLField("ca")} + ) : ( +
+ +
+ {SSLTypeOptions.map((option) => ( +
-
- -
- Input or - + ))} +
+ {sslType !== "none" && ( + <> +
+ setSelectedSSLField("ca")} + > + CA Certificate + + {sslType === "full" && ( + <> + setSelectedSSLField("key")} + > + Client Key + + setSelectedSSLField("cert")} + > + Client Certificate + + + )} +
+
+ +
+ Input or + +
+
+ + )} + {connection.engineType === Engine.MSSQL && ( +
+ +
+
- - )} - {connection.engineType === Engine.MSSQL && ( -
- -
- -
-
- )} -
+ )} +
+ )}
+
{isEditing && ( diff --git a/src/components/EngineIcon.tsx b/src/components/EngineIcon.tsx index 5c031d3..d039048 100644 --- a/src/components/EngineIcon.tsx +++ b/src/components/EngineIcon.tsx @@ -15,6 +15,8 @@ const EngineIcon = (props: Props) => { return ; } else if (engine === Engine.MSSQL) { return ; + } else if (engine === Engine.TiDBServerless) { + return ; } else { return ; } diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index 5b2bb89..62ff452 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -6,6 +6,12 @@ import * as Fi from "react-icons/fi"; import * as Gi from "react-icons/gi"; import * as Io from "react-icons/io"; import * as Io5 from "react-icons/io5"; +import {IconType, IconBaseProps} from "react-icons/lib"; +import {ReactSVG} from "react-svg"; + +const TiDBCloudIcon: IconType = (props: IconBaseProps) => ( + +); const Icon = { ...Ai, @@ -16,6 +22,7 @@ const Icon = { ...Gi, ...Io, ...Io5, + TiDBCloudIcon }; // Icon is a collection of all icons from react-icons. diff --git a/src/locales/en.json b/src/locales/en.json index b51aa07..874d90f 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -34,7 +34,8 @@ "port": "Port", "database-name": "Database name", "username": "Username", - "password": "Password" + "password": "Password", + "tidb-serverless-ssl-hint": "SSL is required and configured" }, "assistant": { "self": "Bot", diff --git a/src/locales/es.json b/src/locales/es.json index f59fe1b..9469a8c 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -32,7 +32,8 @@ "port": "Puerto", "database-name": "Nombre de Base de Datos", "username": "Usuario", - "password": "Contraseña" + "password": "Contraseña", + "tidb-serverless-ssl-hint": "Se requiere SSL y ya está configurado" }, "assistant": { "self": "Bot", diff --git a/src/locales/zh.json b/src/locales/zh.json index 8522076..da71abf 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -34,7 +34,8 @@ "port": "端口", "database-name": "数据库", "username": "用户名", - "password": "密码" + "password": "密码", + "tidb-serverless-ssl-hint": "必需SSL且已配置" }, "assistant": { "self": "机器人", diff --git a/src/pages/api/connection/db.ts b/src/pages/api/connection/db.ts index 84c5ee4..d744dfa 100644 --- a/src/pages/api/connection/db.ts +++ b/src/pages/api/connection/db.ts @@ -1,6 +1,8 @@ import { NextApiRequest, NextApiResponse } from "next"; import { newConnector } from "@/lib/connectors"; import { Connection } from "@/types"; +import { changeTiDBConnectionToMySQL } from "@/utils"; +import { Engine } from "@/types/connection"; // POST /api/connection/db // req body: { connection: Connection } @@ -10,7 +12,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(405).json([]); } - const connection = req.body.connection as Connection; + let connection = req.body.connection as Connection; + if (connection.engineType === Engine.TiDBServerless) { + connection = changeTiDBConnectionToMySQL(connection); + } + try { const connector = newConnector(connection); const databaseNameList = await connector.getDatabases(); diff --git a/src/pages/api/connection/db_schema.ts b/src/pages/api/connection/db_schema.ts index 1c2388d..85644d8 100644 --- a/src/pages/api/connection/db_schema.ts +++ b/src/pages/api/connection/db_schema.ts @@ -1,6 +1,8 @@ import { NextApiRequest, NextApiResponse } from "next"; import { newConnector } from "@/lib/connectors"; import { Connection, Table } from "@/types"; +import { changeTiDBConnectionToMySQL } from "@/utils"; +import { Engine } from "@/types/connection"; // POST /api/connection/db_schema // req body: { connection: Connection, db: string } @@ -10,7 +12,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(405).json([]); } - const connection = req.body.connection as Connection; + let connection = req.body.connection as Connection; + if (connection.engineType === Engine.TiDBServerless) { + connection = changeTiDBConnectionToMySQL(connection); + } + const db = req.body.db as string; try { const connector = newConnector(connection); diff --git a/src/pages/api/connection/execute.ts b/src/pages/api/connection/execute.ts index 6ff613f..e7e7099 100644 --- a/src/pages/api/connection/execute.ts +++ b/src/pages/api/connection/execute.ts @@ -1,6 +1,8 @@ import { NextApiRequest, NextApiResponse } from "next"; import { newConnector } from "@/lib/connectors"; import { Connection } from "@/types"; +import { changeTiDBConnectionToMySQL } from "@/utils"; +import { Engine } from "@/types/connection"; // POST /api/connection/execute // req body: { connection: Connection, db: string, statement: string } @@ -9,7 +11,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(405).json(false); } - const connection = req.body.connection as Connection; + let connection = req.body.connection as Connection; + if (connection.engineType === Engine.TiDBServerless) { + connection = changeTiDBConnectionToMySQL(connection); + } const db = req.body.db as string; const statement = req.body.statement as string; diff --git a/src/pages/api/connection/test.ts b/src/pages/api/connection/test.ts index d9ec669..8726d46 100644 --- a/src/pages/api/connection/test.ts +++ b/src/pages/api/connection/test.ts @@ -1,6 +1,8 @@ import { NextApiRequest, NextApiResponse } from "next"; import { newConnector } from "@/lib/connectors"; import { Connection } from "@/types"; +import { changeTiDBConnectionToMySQL } from "@/utils"; +import { Engine } from "@/types/connection"; // POST /api/connection/test // req body: { connection: Connection } @@ -10,7 +12,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return res.status(405).json(false); } - const connection = req.body.connection as Connection; + let connection = req.body.connection as Connection; + if (connection.engineType === Engine.TiDBServerless) { + connection = changeTiDBConnectionToMySQL(connection); + } try { const connector = newConnector(connection); await connector.testConnection(); diff --git a/src/types/connection.ts b/src/types/connection.ts index 522eba5..14807c7 100644 --- a/src/types/connection.ts +++ b/src/types/connection.ts @@ -4,12 +4,15 @@ export enum Engine { MySQL = "MYSQL", PostgreSQL = "POSTGRESQL", MSSQL = "MSSQL", + TiDBServerless = "TiDBServerless", } export interface SSLOptions { ca?: string; cert?: string; key?: string; + minVersion?: string; + rejectUnauthorized?: boolean; } export interface Connection { diff --git a/src/utils/index.ts b/src/utils/index.ts index 9d730bf..285d658 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,3 +4,4 @@ export * from "./sql"; export * from "./execution"; export * from "./model"; export * from "./feature"; +export * from "./tidb"; \ No newline at end of file diff --git a/src/utils/tidb.ts b/src/utils/tidb.ts new file mode 100644 index 0000000..3a03585 --- /dev/null +++ b/src/utils/tidb.ts @@ -0,0 +1,16 @@ +import { Connection } from "@/types"; +import { Engine } from "@/types/connection"; + +export const changeTiDBConnectionToMySQL = (connection: Connection) => { + if (connection.engineType === Engine.TiDBServerless) { + return { + ...connection, + engineType: Engine.MySQL, + ssl: { + minVersion: 'TLSv1.2', + rejectUnauthorized: true + } + }; + } + return connection; +} \ No newline at end of file