From 463d330f3c0218b9838cc592d4f0044369f764e3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 1 Nov 2023 21:27:28 +0800 Subject: [PATCH 001/126] open mongo --- chat2db-client/src/constants/database.ts | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index 8afa48045..ea92466d3 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -97,13 +97,6 @@ export const databaseMap: { // port: 2883, icon: '\ue982', }, - // [DatabaseTypeCode.REDIS]: { - // name: 'Redis', - // img: moreDBLogo, - // code: DatabaseTypeCode.REDIS, - // // port: 6379, - // icon: '\ue6a2', - // }, [DatabaseTypeCode.HIVE]: { name: 'Hive', img: moreDBLogo, @@ -118,13 +111,20 @@ export const databaseMap: { // port: 54321, icon: '\ue6a0', }, - // [DatabaseTypeCode.MONGODB]: { - // name: 'MongoDB', - // img: moreDBLogo, - // code: DatabaseTypeCode.MONGODB, - // // port: 27017, - // icon: '\uec21', - // }, + [DatabaseTypeCode.MONGODB]: { + name: 'MongoDB', + img: moreDBLogo, + code: DatabaseTypeCode.MONGODB, + // port: 27017, + icon: '\uec21', + }, + [DatabaseTypeCode.REDIS]: { + name: 'Redis', + img: moreDBLogo, + code: DatabaseTypeCode.REDIS, + // port: 6379, + icon: '\ue6a2', + }, }; export const databaseTypeList = Object.keys(databaseMap).map((keys) => { From 85df18755058508ccf1f0175bdbc73c8c1f67ebd Mon Sep 17 00:00:00 2001 From: evrentan Date: Fri, 10 Nov 2023 10:42:50 +0400 Subject: [PATCH 002/126] fix(IDriverManager): fix 645 issue https://github.com/chat2db/Chat2DB/issues/645 --- .../ai/chat2db/spi/sql/IDriverManager.java | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java index d841646a0..064e9efd7 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java @@ -1,6 +1,15 @@ package ai.chat2db.spi.sql; +import ai.chat2db.server.tools.common.exception.ConnectionException; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.DriverEntry; +import ai.chat2db.spi.util.JdbcJarUtils; +import com.alibaba.fastjson2.JSON; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.File; import java.net.MalformedURLException; import java.net.URL; @@ -9,19 +18,10 @@ import java.sql.Driver; import java.sql.SQLException; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; -import com.alibaba.fastjson2.JSON; - -import ai.chat2db.server.tools.common.exception.ConnectionException; -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.DriverEntry; -import ai.chat2db.spi.util.JdbcJarUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import static ai.chat2db.spi.util.JdbcJarUtils.getFullPath; /** @@ -32,6 +32,7 @@ public class IDriverManager { private static final Logger log = LoggerFactory.getLogger(IDriverManager.class); private static final Map CLASS_LOADER_MAP = new ConcurrentHashMap(); private static final Map DRIVER_ENTRY_MAP = new ConcurrentHashMap(); + private static final String SQL_STATE_CODE = "08001"; public static Connection getConnection(String url, DriverConfig driver) throws SQLException { Properties info = new Properties(); @@ -75,27 +76,29 @@ public static Connection getConnection(String url, String user, String password, public static Connection getConnection(String url, Properties info, DriverConfig driver) throws SQLException { - if (url == null) { - throw new SQLException("The url cannot be null", "08001"); + if (Objects.isNull(url)) { + throw new SQLException("The url cannot be null", SQL_STATE_CODE); } + DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); - if (driverEntry == null) { + if (Objects.isNull(driverEntry)) { driverEntry = getJDBCDriver(driver); } - try { - Connection connection = driverEntry.getDriver().connect(url, info); - if(connection == null){ - throw new SQLException("driver.connect return null , No suitable driver found for url " +url ,"08001"); + + try (Connection connection = driverEntry.getDriver().connect(url, info)) { + if (Objects.isNull(connection)) { + throw new SQLException(String.format("driver.connect return null , No suitable driver found for url %s", url), SQL_STATE_CODE); } return connection; - } catch (SQLException var7) { + } catch (SQLException sqlException) { Connection con = tryConnectionAgain(driverEntry, url, info); - if (con != null) { - return con; - } else { - throw new SQLException("Cannot create connection (" + var7.getMessage() + ")", "08001", - var7); + + if (Objects.isNull(con)) { + throw new SQLException(String.format("Cannot create connection (%s)", sqlException.getMessage()), SQL_STATE_CODE, + sqlException); } + + return con; } } From 811bb2be03437889ce1e9868faca5801a7a82a67 Mon Sep 17 00:00:00 2001 From: tmlx1990 Date: Sun, 12 Nov 2023 00:16:22 +0800 Subject: [PATCH 003/126] =?UTF-8?q?FIX:=20772=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E5=80=BC=E4=B8=AD=E5=A2=9E=E5=8A=A0CURRENT=5FTIMESTAMP?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/blocks/DatabaseTableEditor/ColumnList/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index b46633a44..b013e8f78 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -513,6 +513,10 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) label: 'NULL', value: 'NULL', }, + { + label: 'CURRENT_TIMESTAMP', + value: 'CURRENT_TIMESTAMP', + } ]} /> From f89032e0df9ace851e41a1355067243ef9ef4da2 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 16:36:30 +0800 Subject: [PATCH 004/126] chore: Optimize action --- .github/workflows/release_test copy.yml | 6 +++--- .github/workflows/release_test.yml | 19 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release_test copy.yml b/.github/workflows/release_test copy.yml index ddbad6d55..db0f0d1e6 100644 --- a/.github/workflows/release_test copy.yml +++ b/.github/workflows/release_test copy.yml @@ -6,9 +6,9 @@ name: Build Test Client on: push: branches: - - "release_test" - - "release_test_2" - - "release_test_3" + # - "release_test" + # - "release_test_2" + # - "release_test_3" # Workflow's jobs # 一共需要3台电脑运行 diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index ff00fb057..86335b349 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -11,14 +11,14 @@ jobs: fail-fast: false matrix: include: - - os: windows-latest - file_extension: ".exe" - - os: macos-latest - arch: x86_64 - file_extension: ".dmg" - - os: macos-latest - arch: arm64 - file_extension: ".dmg" + # - os: windows-latest + # file_extension: ".exe" + # - os: macos-latest + # arch: x86_64 + # file_extension: ".dmg" + # - os: macos-latest + # arch: arm64 + # file_extension: ".dmg" - os: ubuntu-latest file_extension: ".AppImage" runs-on: ${{ matrix.os }} @@ -46,7 +46,6 @@ jobs: fi env: JAVA_HOME: ${{ env.JAVA_HOME }} - shell: bash - name: Copy JRE to static directory run: | @@ -56,7 +55,7 @@ jobs: chmod -R 777 chat2db-client/static/jre fi env: - JAVA_HOME: ${{ runner.os == 'Windows' && env.JAVA_HOME }} || $JAVA_HOME + JAVA_HOME: ${{ env.JAVA_HOME }} - name: Install Node.js uses: actions/setup-node@main From bd134309411439dc057ae6d75cd34e8fe7d9e5bf Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 16:39:05 +0800 Subject: [PATCH 005/126] chore: Optimize action --- .github/workflows/release_test copy.yml | 2 +- .github/workflows/release_test.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_test copy.yml b/.github/workflows/release_test copy.yml index db0f0d1e6..4dc80ee7a 100644 --- a/.github/workflows/release_test copy.yml +++ b/.github/workflows/release_test copy.yml @@ -6,7 +6,7 @@ name: Build Test Client on: push: branches: - # - "release_test" + - "release_test" # - "release_test_2" # - "release_test_3" diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 86335b349..f82dc6ce1 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -2,8 +2,8 @@ name: Build Test Client on: push: - branches: - - "release_test" + # branches: + # - "release_test" jobs: release: From 574d4f6d853d3004bdbb6c435fbe161001b232ef Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 16:39:45 +0800 Subject: [PATCH 006/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index f82dc6ce1..d9cd5c22d 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -38,7 +38,7 @@ jobs: - name: Enable TLS 1.0 and 1.1 in java.security run: | if [[ "${{ runner.os }}" == "Windows" ]]; then - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security elseif [[ "${{ runner.os }}" == "Linux" ]]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security else From 29c656a4935c6cf3d5026b2931114cdc1bc78f96 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 16:41:38 +0800 Subject: [PATCH 007/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index d9cd5c22d..730bc2155 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -39,7 +39,7 @@ jobs: run: | if [[ "${{ runner.os }}" == "Windows" ]]; then sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security - elseif [[ "${{ runner.os }}" == "Linux" ]]; then + elif [[ "${{ runner.os }}" == "Linux" ]]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security else sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security From aee0a809fbb1cfd177340a55fa12ffed5281cab5 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 16:50:37 +0800 Subject: [PATCH 008/126] chore: Optimize action --- .github/workflows/release_test.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 730bc2155..397434dc1 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -64,6 +64,7 @@ jobs: cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock + - name: Install Java and Maven uses: actions/setup-java@main with: @@ -71,6 +72,18 @@ jobs: distribution: "temurin" cache: "maven" + - name: Cache Yarn dependencies + uses: actions/cache@v3 + with: + path: | + .yarn/cache + .yarn/unplugged + .yarn/build-state.yml + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Build and Copy Artifacts run: | cd chat2db-client @@ -81,7 +94,6 @@ jobs: mvn clean package -B '-Dmaven.test.skip=true' -f ../chat2db-server/pom.xml mkdir -p versions/99.0.${{ github.run_id }}/static echo "99.0.${{ github.run_id }}" > versions/version - cp -r versions/version ./versions/ cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ - name: Prepare Build Electron From c2b4687cf34afa396708165bb4d91b4c0b9ad3f1 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 16:54:21 +0800 Subject: [PATCH 009/126] chore: Optimize action --- .github/workflows/release_test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 397434dc1..5c0d4297c 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -80,9 +80,10 @@ jobs: .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.os }}-yarn- + yarn-${{ runner.os }}- + yarn- - name: Build and Copy Artifacts run: | From e6389c7609f6a9f8b7fb11519978638e24ad1004 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 16:59:27 +0800 Subject: [PATCH 010/126] chore: Optimize action --- .github/workflows/release_test.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 5c0d4297c..16cbf1fd6 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -85,16 +85,20 @@ jobs: yarn-${{ runner.os }}- yarn- - - name: Build and Copy Artifacts + - name: Build FE Static run: | cd chat2db-client yarn install yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ - mvn clean package -B '-Dmaven.test.skip=true' -f ../chat2db-server/pom.xml - mkdir -p versions/99.0.${{ github.run_id }}/static - echo "99.0.${{ github.run_id }}" > versions/version + - name: Build BE Static + run: | + mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml + cd chat2db-client + mkdir -p chat2db-client/versions/99.0.${{ github.run_id }}/static + echo -n "99.0.${{ github.run_id }}" > chat2db-client/versions/99.0.${{ github.run_id }}/version + cd .. cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ - name: Prepare Build Electron From 361e1a1f63e64862a4d37626fd17b814afef4ca7 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:01:58 +0800 Subject: [PATCH 011/126] chore: Optimize action --- .github/workflows/release_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 16cbf1fd6..4b0dea517 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -92,13 +92,13 @@ jobs: yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ + - name: Build BE Static run: | mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml - cd chat2db-client mkdir -p chat2db-client/versions/99.0.${{ github.run_id }}/static - echo -n "99.0.${{ github.run_id }}" > chat2db-client/versions/99.0.${{ github.run_id }}/version - cd .. + echo -n 99.0.${{ github.run_id }} > chat2db-client/version + cp -r chat2db-client/version chat2db-client/versions/ cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ - name: Prepare Build Electron From f8f4b57a8fcb4a4ccf950fce4018e45aab433e98 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:08:09 +0800 Subject: [PATCH 012/126] chore: Optimize action --- .github/workflows/release_test.yml | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 4b0dea517..c8682c2a1 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -11,14 +11,14 @@ jobs: fail-fast: false matrix: include: - # - os: windows-latest - # file_extension: ".exe" - # - os: macos-latest - # arch: x86_64 - # file_extension: ".dmg" - # - os: macos-latest - # arch: arm64 - # file_extension: ".dmg" + - os: windows-latest + file_extension: ".exe" + - os: macos-latest + arch: x86_64 + file_extension: ".dmg" + - os: macos-latest + arch: arm64 + file_extension: ".dmg" - os: ubuntu-latest file_extension: ".AppImage" runs-on: ${{ matrix.os }} @@ -75,23 +75,19 @@ jobs: - name: Cache Yarn dependencies uses: actions/cache@v3 with: - path: | - .yarn/cache - .yarn/unplugged - .yarn/build-state.yml - .yarn/install-state.gz + path: '**/node_modules' key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} restore-keys: | yarn-${{ runner.os }}- - yarn- - name: Build FE Static run: | cd chat2db-client - yarn install + yarn install --frozen-lockfile yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ + - name: Build BE Static run: | @@ -108,6 +104,15 @@ jobs: cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist + # Linux + - name: Delete File + if: ${{ runner.os == 'Linux' }} + run: | + cd chat2db-client/static/jre/ + ls -la + rm -rf legal + ls -la + - name: Build/release Electron app uses: samuelmeuli/action-electron-builder@v1 with: From f163bd59db60ebc8d96a06a8d9aa9424a1292278 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:15:41 +0800 Subject: [PATCH 013/126] chore: Optimize action --- .github/workflows/release_test.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index c8682c2a1..cf1e47785 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -3,7 +3,7 @@ name: Build Test Client on: push: # branches: - # - "release_test" + # - "release_test" jobs: release: @@ -38,7 +38,7 @@ jobs: - name: Enable TLS 1.0 and 1.1 in java.security run: | if [[ "${{ runner.os }}" == "Windows" ]]; then - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [[ "${{ runner.os }}" == "Linux" ]]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security else @@ -64,7 +64,6 @@ jobs: cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock - - name: Install Java and Maven uses: actions/setup-java@main with: @@ -75,11 +74,11 @@ jobs: - name: Cache Yarn dependencies uses: actions/cache@v3 with: - path: '**/node_modules' + path: "**/node_modules" key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} restore-keys: | yarn-${{ runner.os }}- - + - name: Build FE Static run: | cd chat2db-client @@ -88,7 +87,6 @@ jobs: cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ - - name: Build BE Static run: | mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml @@ -118,6 +116,9 @@ jobs: with: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + mac_certs: ${{ secrets.mac_certs }} + mac_certs_password: ${{ secrets.mac_certs_password }} + skip_build: true args: > -c.appId=com.chat2db.test -c.productName=Chat2DB-Test From f4a376e1d58fa943d512b9465e85d5fdcd34b91b Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:29:29 +0800 Subject: [PATCH 014/126] chore: Optimize action --- .github/workflows/release_test.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index cf1e47785..321788894 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -13,14 +13,18 @@ jobs: include: - os: windows-latest file_extension: ".exe" + build_arg: "--win --x64" - os: macos-latest arch: x86_64 file_extension: ".dmg" + build_arg: "--mac --x64" - os: macos-latest arch: arm64 file_extension: ".dmg" + build_arg: "--mac --arm64" - os: ubuntu-latest file_extension: ".AppImage" + build_arg: "--linux" runs-on: ${{ matrix.os }} steps: @@ -38,14 +42,15 @@ jobs: - name: Enable TLS 1.0 and 1.1 in java.security run: | if [[ "${{ runner.os }}" == "Windows" ]]; then - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" elif [[ "${{ runner.os }}" == "Linux" ]]; then - sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" ${{ env.JAVA_HOME }}/conf/security/java.security else sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security fi - env: - JAVA_HOME: ${{ env.JAVA_HOME }} + shell: bash + + - name: Copy JRE to static directory run: | @@ -74,10 +79,12 @@ jobs: - name: Cache Yarn dependencies uses: actions/cache@v3 with: - path: "**/node_modules" - key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + path: | + ${{ runner.temp }}/yarn + ${{ runner.temp }}/node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | - yarn-${{ runner.os }}- + ${{ runner.os }}-yarn- - name: Build FE Static run: | @@ -125,7 +132,7 @@ jobs: -c.win.publisherName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test - --${{ matrix.os }} ${{ matrix.arch }} + ${{ matrix.build_arg}} # Only for macOS x86_64 - name: Notarize MacOS x86_64 App From 59069357e217aa15c12cff03b7da06ed791faa9a Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:32:43 +0800 Subject: [PATCH 015/126] chore: Optimize action --- .github/workflows/release_test.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 321788894..bd8ab5366 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -41,16 +41,18 @@ jobs: - name: Enable TLS 1.0 and 1.1 in java.security run: | - if [[ "${{ runner.os }}" == "Windows" ]]; then - sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" - elif [[ "${{ runner.os }}" == "Linux" ]]; then - sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" ${{ env.JAVA_HOME }}/conf/security/java.security - else - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security + if [ "$RUNNER_OS" = "Windows" ]; then + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" + elif [ "$RUNNER_OS" = "Linux" ]; then + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" + elif [ "$RUNNER_OS" = "macOS" ]; then + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" fi shell: bash - - + env: + RUNNER_OS: ${{ runner.os }} + JAVA_HOME: ${{ env.JAVA_HOME }} + - name: Copy JRE to static directory run: | From c8f0e00087e83614b82fb67327c6610d1d1a7545 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:34:25 +0800 Subject: [PATCH 016/126] chore: Optimize action --- .github/workflows/release_test.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index bd8ab5366..aed14f2c0 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -39,11 +39,16 @@ jobs: java-package: "jre" architecture: ${{ matrix.arch }} + # java.security 开放tls1 Windows + - name: Enable tls1 + if: ${{ runner.os == 'Windows' }} + run: | + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" + - name: Enable TLS 1.0 and 1.1 in java.security run: | - if [ "$RUNNER_OS" = "Windows" ]; then - sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" - elif [ "$RUNNER_OS" = "Linux" ]; then + + if [ "$RUNNER_OS" = "Linux" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "macOS" ]; then sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" From 14462755eb12dd1f79ea263d8b2e47c12b38b37d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:35:31 +0800 Subject: [PATCH 017/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index aed14f2c0..70959e23e 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -46,8 +46,8 @@ jobs: sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" - name: Enable TLS 1.0 and 1.1 in java.security + if: ${{ runner.os != 'Windows' }} run: | - if [ "$RUNNER_OS" = "Linux" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "macOS" ]; then From 6ed7ad811f6a11019a4a5e975e63416843493594 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:41:07 +0800 Subject: [PATCH 018/126] chore: Optimize action --- .github/workflows/release_test.yml | 34 +++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 70959e23e..efe2b5dec 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -62,10 +62,11 @@ jobs: - name: Copy JRE to static directory run: | mkdir -p chat2db-client/static/jre - cp -r "${JAVA_HOME}" chat2db-client/static/jre - if [[ "${{ runner.os }}" != "Windows" ]]; then + cp -r "$JAVA_HOME" chat2db-client/static/jre + if [ "${{ runner.os }}" != "Windows" ]; then chmod -R 777 chat2db-client/static/jre fi + shell: bash env: JAVA_HOME: ${{ env.JAVA_HOME }} @@ -83,15 +84,23 @@ jobs: distribution: "temurin" cache: "maven" - - name: Cache Yarn dependencies - uses: actions/cache@v3 + - name: Cache Yarn Dependencies + uses: actions/cache@v2 + with: + path: | + ~/.cache/yarn + node_modules + key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} + + - name: Restore Yarn Cache + uses: actions/cache@v2 with: path: | - ${{ runner.temp }}/yarn - ${{ runner.temp }}/node_modules - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + ~/.cache/yarn + node_modules + key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.os }}-yarn- + {{ runner.os }}-yarn-{{ runner.os }}- - name: Build FE Static run: | @@ -188,3 +197,12 @@ jobs: "title": "${{ matrix.os }}-test-打包完成通知", "text": "# ${{ matrix.os }}-test-打包完成通知\n ![bang](https://oss.sqlgpt.cn/static/bang100.gif)\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }}](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }})" } + + - name: Save Yarn Cache + uses: actions/cache@v2 + with: + path: | + ~/.cache/yarn + node_modules + key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} + save-key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} From 4772f49cc3808eff7ea007ca881103291cec7159 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 17:49:46 +0800 Subject: [PATCH 019/126] chore: Optimize action --- .github/workflows/release_test.yml | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index efe2b5dec..629b06071 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -57,7 +57,6 @@ jobs: env: RUNNER_OS: ${{ runner.os }} JAVA_HOME: ${{ env.JAVA_HOME }} - - name: Copy JRE to static directory run: | @@ -84,23 +83,14 @@ jobs: distribution: "temurin" cache: "maven" - - name: Cache Yarn Dependencies - uses: actions/cache@v2 - with: - path: | - ~/.cache/yarn - node_modules - key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} - - name: Restore Yarn Cache uses: actions/cache@v2 with: path: | - ~/.cache/yarn - node_modules - key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} + ./node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | - {{ runner.os }}-yarn-{{ runner.os }}- + ${{ runner.os }}-yarn- - name: Build FE Static run: | @@ -197,12 +187,3 @@ jobs: "title": "${{ matrix.os }}-test-打包完成通知", "text": "# ${{ matrix.os }}-test-打包完成通知\n ![bang](https://oss.sqlgpt.cn/static/bang100.gif)\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }}](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }})" } - - - name: Save Yarn Cache - uses: actions/cache@v2 - with: - path: | - ~/.cache/yarn - node_modules - key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} - save-key: {{ runner.os }}-yarn-{{ hashFiles('**/yarn.lock') }} From 7ca8968d06fa9aadc9f9d3cbcd097119399b2832 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 18:05:37 +0800 Subject: [PATCH 020/126] chore: Optimize action --- .github/workflows/release_test.yml | 55 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 629b06071..7f893e805 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -39,15 +39,10 @@ jobs: java-package: "jre" architecture: ${{ matrix.arch }} - # java.security 开放tls1 Windows - - name: Enable tls1 - if: ${{ runner.os == 'Windows' }} - run: | - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" - - name: Enable TLS 1.0 and 1.1 in java.security - if: ${{ runner.os != 'Windows' }} run: | + if [ "$RUNNER_OS" = "Windows" ]; then + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" if [ "$RUNNER_OS" = "Linux" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "macOS" ]; then @@ -73,8 +68,8 @@ jobs: uses: actions/setup-node@main with: node-version: "16" - cache: "yarn" - cache-dependency-path: chat2db-client/yarn.lock + # cache: "yarn" + # cache-dependency-path: chat2db-client/yarn.lock - name: Install Java and Maven uses: actions/setup-java@main @@ -83,23 +78,42 @@ jobs: distribution: "temurin" cache: "maven" - - name: Restore Yarn Cache - uses: actions/cache@v2 + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cache-node-modules with: - path: | - ./node_modules - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.os }}-yarn- + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} + name: List the state of node modules + continue-on-error: true + run: | + cd chat2db-client + yarn install --frozen-lockfile - name: Build FE Static run: | cd chat2db-client - yarn install --frozen-lockfile yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ + # Linux + - if: ${{ runner.os == 'Linux' }} + name: Delete File + run: | + cd chat2db-client/static/jre/ + ls -la + rm -rf legal + ls -la + - name: Build BE Static run: | mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml @@ -115,14 +129,7 @@ jobs: cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist - # Linux - - name: Delete File - if: ${{ runner.os == 'Linux' }} - run: | - cd chat2db-client/static/jre/ - ls -la - rm -rf legal - ls -la + - name: Build/release Electron app uses: samuelmeuli/action-electron-builder@v1 From 70983637d5e2a9b0b06987fa5d2a4b457c912f4a Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 18:08:57 +0800 Subject: [PATCH 021/126] chore: Optimize action --- .github/workflows/release_test.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 7f893e805..cd331b49d 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -96,7 +96,7 @@ jobs: continue-on-error: true run: | cd chat2db-client - yarn install --frozen-lockfile + yarn install --frozen-lockfile - name: Build FE Static run: | @@ -113,7 +113,7 @@ jobs: ls -la rm -rf legal ls -la - + - name: Build BE Static run: | mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml @@ -129,8 +129,6 @@ jobs: cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist - - - name: Build/release Electron app uses: samuelmeuli/action-electron-builder@v1 with: From 052ab124901b736798baab4243ab43e26967f4a4 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 18:11:29 +0800 Subject: [PATCH 022/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index cd331b49d..61d37b766 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -43,7 +43,7 @@ jobs: run: | if [ "$RUNNER_OS" = "Windows" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" - if [ "$RUNNER_OS" = "Linux" ]; then + elif [ "$RUNNER_OS" = "Linux" ]; then sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" elif [ "$RUNNER_OS" = "macOS" ]; then sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" From a7b87e2faac697fe5b7aefc16cdba876674ad984 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 18:24:08 +0800 Subject: [PATCH 023/126] chore: Optimize action --- .github/workflows/release_test.yml | 41 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 61d37b766..241fe2e3e 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -31,6 +31,7 @@ jobs: - name: Check out git repository uses: actions/checkout@main + # 安装JRE - name: Install JRE uses: actions/setup-java@main with: @@ -39,6 +40,7 @@ jobs: java-package: "jre" architecture: ${{ matrix.arch }} + # 开放TLS - name: Enable TLS 1.0 and 1.1 in java.security run: | if [ "$RUNNER_OS" = "Windows" ]; then @@ -53,6 +55,7 @@ jobs: RUNNER_OS: ${{ runner.os }} JAVA_HOME: ${{ env.JAVA_HOME }} + # JRE拷贝到前端静态目录 - name: Copy JRE to static directory run: | mkdir -p chat2db-client/static/jre @@ -64,6 +67,16 @@ jobs: env: JAVA_HOME: ${{ env.JAVA_HOME }} + # Linux中删除jre中相关文件 + - if: ${{ runner.os == 'Linux' }} + name: Delete File on Linux + run: | + cd chat2db-client/static/jre/ + ls -la + rm -rf legal + ls -la + + # 安装Node.js - name: Install Node.js uses: actions/setup-node@main with: @@ -71,6 +84,7 @@ jobs: # cache: "yarn" # cache-dependency-path: chat2db-client/yarn.lock + # 安装Java - name: Install Java and Maven uses: actions/setup-java@main with: @@ -78,6 +92,7 @@ jobs: distribution: "temurin" cache: "maven" + # 从缓存中拿去前端依赖 - name: Cache node modules id: cache-npm uses: actions/cache@v3 @@ -85,19 +100,21 @@ jobs: cache-name: cache-node-modules with: path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- + # 安装前端依赖 - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: List the state of node modules + name: Yarn install continue-on-error: true run: | cd chat2db-client yarn install --frozen-lockfile + # 打包Web前端资源 - name: Build FE Static run: | cd chat2db-client @@ -105,15 +122,7 @@ jobs: cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ - # Linux - - if: ${{ runner.os == 'Linux' }} - name: Delete File - run: | - cd chat2db-client/static/jre/ - ls -la - rm -rf legal - ls -la - + # 打包后端工程 & 发送到前端 - name: Build BE Static run: | mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml @@ -122,6 +131,7 @@ jobs: cp -r chat2db-client/version chat2db-client/versions/ cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ + # 打包桌面端前端资源 - name: Prepare Build Electron run: | cd chat2db-client @@ -129,6 +139,7 @@ jobs: cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist + # 打包Electron - name: Build/release Electron app uses: samuelmeuli/action-electron-builder@v1 with: @@ -145,7 +156,7 @@ jobs: -c.extraMetadata.version=99.0.${{ github.run_id }}-Test ${{ matrix.build_arg}} - # Only for macOS x86_64 + # 公证&签名 Mac App - name: Notarize MacOS x86_64 App if: matrix.os == 'macos-latest' && matrix.arch == 'x86_64' run: | @@ -165,11 +176,13 @@ jobs: cd static/ && zip -r chat2db-server-start.zip ./ cp -r chat2db-server-start.zip ../../../../oss_temp_file + # 准备发往OSS的文件 - name: Prepare upload for OSS run: | mkdir -p oss_temp_file cp -r chat2db-client/release/*${{ matrix.file_extension }} ./oss_temp_file + # 设置OSS - name: Set up oss utils uses: yizhoumo/setup-ossutil@v1 with: @@ -177,11 +190,11 @@ jobs: access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} ossutil-version: "1.7.16" - + # 上传到OSS - name: Upload to OSS run: | ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.0.${{ github.run_id }}/ - + # 发送到DingTalk - name: Send dingtalk message uses: ghostoy/dingtalk-action@master with: From c91f5a1b95e3d82e060170d76897702e487f6b16 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 18:35:01 +0800 Subject: [PATCH 024/126] chore: Optimize action --- .github/workflows/release_test.yml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 241fe2e3e..62d9d37e4 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -81,8 +81,8 @@ jobs: uses: actions/setup-node@main with: node-version: "16" - # cache: "yarn" - # cache-dependency-path: chat2db-client/yarn.lock + cache: "yarn" + cache-dependency-path: chat2db-client/yarn.lock # 安装Java - name: Install Java and Maven @@ -93,18 +93,17 @@ jobs: cache: "maven" # 从缓存中拿去前端依赖 - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + # - name: Cache node modules + # uses: actions/cache@v3 + # env: + # cache-name: cache-node-modules + # with: + # path: ./node_modules + # key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} + # restore-keys: | + # ${{ runner.os }}-build-${{ env.cache-name }}- + # ${{ runner.os }}-build- + # ${{ runner.os }}- # 安装前端依赖 - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} From a084facc44ec4a733ddc2a47503d942c2554688f Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 18:44:13 +0800 Subject: [PATCH 025/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 62d9d37e4..9e97e1f3b 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -71,7 +71,7 @@ jobs: - if: ${{ runner.os == 'Linux' }} name: Delete File on Linux run: | - cd chat2db-client/static/jre/ + cd chat2db-client/static/jre/x64/ ls -la rm -rf legal ls -la From 2c2eebb58b2d5efcee8c1544ab1d48414126c852 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 20:00:43 +0800 Subject: [PATCH 026/126] chore: Optimize action --- .github/workflows/release_test.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 9e97e1f3b..d8daaabaf 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -204,3 +204,16 @@ jobs: "title": "${{ matrix.os }}-test-打包完成通知", "text": "# ${{ matrix.os }}-test-打包完成通知\n ![bang](https://oss.sqlgpt.cn/static/bang100.gif)\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }}](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }})" } + + # 发送Jar包地址到DingTalk + - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} + name: Send dingtalk message + uses: ghostoy/dingtalk-action@master + with: + webhook: ${{ secrets.DINGTALK_WEBHOOK }} + msgtype: markdown + content: | + { + "title": "Jar-test-构建完成通知", + "text": "### jar包下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip) " + } From 53efb593cc6899388b63ca0f405e3e89cf5e321b Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 20:26:58 +0800 Subject: [PATCH 027/126] chore: Optimize action --- .github/workflows/release_test.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index d8daaabaf..231fd96a5 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -13,16 +13,20 @@ jobs: include: - os: windows-latest file_extension: ".exe" + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe" build_arg: "--win --x64" - os: macos-latest arch: x86_64 + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg" file_extension: ".dmg" build_arg: "--mac --x64" - os: macos-latest arch: arm64 + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg" file_extension: ".dmg" build_arg: "--mac --arm64" - os: ubuntu-latest + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage" file_extension: ".AppImage" build_arg: "--linux" runs-on: ${{ matrix.os }} @@ -189,10 +193,12 @@ jobs: access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} ossutil-version: "1.7.16" + # 上传到OSS - name: Upload to OSS run: | ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.0.${{ github.run_id }}/ + # 发送到DingTalk - name: Send dingtalk message uses: ghostoy/dingtalk-action@master @@ -202,9 +208,9 @@ jobs: content: | { "title": "${{ matrix.os }}-test-打包完成通知", - "text": "# ${{ matrix.os }}-test-打包完成通知\n ![bang](https://oss.sqlgpt.cn/static/bang100.gif)\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }}](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ matrix.os }}-Test${{ matrix.file_extension }})" + "text": "# ${{ matrix.os }}-test-打包完成通知\n ![bang](https://oss.sqlgpt.cn/static/bang100.gif)\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[${{matrix.file_name}}](${{matrix.file_name}})" } - + # 发送Jar包地址到DingTalk - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} name: Send dingtalk message From b15576cadac612fdb489951657a1e62ef8ae160d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 20:52:33 +0800 Subject: [PATCH 028/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 231fd96a5..2fa7a9258 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -42,7 +42,7 @@ jobs: java-version: "17" distribution: "temurin" java-package: "jre" - architecture: ${{ matrix.arch }} + architecture: ${{ matrix.arch == 'arm64' && 'aarch64' }} # 开放TLS - name: Enable TLS 1.0 and 1.1 in java.security From 7479bb7dfd2081697ab6d87687810b18f5a8c395 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 21:05:04 +0800 Subject: [PATCH 029/126] chore: Optimize action --- .github/workflows/release_test.yml | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 2fa7a9258..a1d69d782 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -42,7 +42,7 @@ jobs: java-version: "17" distribution: "temurin" java-package: "jre" - architecture: ${{ matrix.arch == 'arm64' && 'aarch64' }} + architecture: ${{ matrix.arch == 'arm64' && 'aarch64' || 'x64' }} # 开放TLS - name: Enable TLS 1.0 and 1.1 in java.security @@ -96,31 +96,11 @@ jobs: distribution: "temurin" cache: "maven" - # 从缓存中拿去前端依赖 - # - name: Cache node modules - # uses: actions/cache@v3 - # env: - # cache-name: cache-node-modules - # with: - # path: ./node_modules - # key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }} - # restore-keys: | - # ${{ runner.os }}-build-${{ env.cache-name }}- - # ${{ runner.os }}-build- - # ${{ runner.os }}- - - # 安装前端依赖 - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: Yarn install - continue-on-error: true - run: | - cd chat2db-client - yarn install --frozen-lockfile - # 打包Web前端资源 - name: Build FE Static run: | cd chat2db-client + yarn install --frozen-lockfile yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ From 51db80870e76811ef1c96a701c76fa8f8859d5b3 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 21:18:58 +0800 Subject: [PATCH 030/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index a1d69d782..1bbbff05d 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -42,7 +42,7 @@ jobs: java-version: "17" distribution: "temurin" java-package: "jre" - architecture: ${{ matrix.arch == 'arm64' && 'aarch64' || 'x64' }} + # architecture: ${{ matrix.arch == 'arm64' && 'aarch64' || 'x64' }} # 开放TLS - name: Enable TLS 1.0 and 1.1 in java.security From d02abb4fb9e77081fa4b472eae7c1a849a9c3ce7 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 12 Nov 2023 21:26:31 +0800 Subject: [PATCH 031/126] refactor-workspace --- .vscode/settings.json | 1 + .../src/blocks/CreateConnection/index.less | 38 +- .../src/blocks/CreateConnection/index.tsx | 103 +++-- .../src/blocks/SQLExecute/index.tsx | 18 - .../Tree/TreeNodeRightClick/index.less | 9 +- .../Tree/TreeNodeRightClick/index.tsx | 232 +++++----- .../src/blocks/Tree/functions/refresh.ts | 8 + .../src/blocks/Tree/hooks/useTreeNodeFocus.ts | 6 + .../components => blocks}/Tree/index.less | 74 ++- chat2db-client/src/blocks/Tree/index.tsx | 225 +++++++++ .../src/blocks/Tree/screenOutMenu.ts | 75 +++ .../components => blocks}/Tree/treeConfig.tsx | 145 ++++-- chat2db-client/src/blocks/Tree/treeStore.ts | 21 + .../src/components/ConnectionEdit/index.less | 13 +- .../src/components/ConnectionEdit/index.tsx | 158 +++---- .../src/components/MenuLabel/index.less | 9 +- .../src/components/MenuLabel/index.tsx | 20 +- chat2db-client/src/constants/environment.ts | 4 - .../src/hooks/useClickAndDoubleClick.ts | 28 ++ chat2db-client/src/hooks/useGetConnection.ts | 56 +++ chat2db-client/src/i18n/en-us/connection.ts | 1 + chat2db-client/src/i18n/zh-cn/connection.ts | 1 + chat2db-client/src/models/connection.ts | 99 ---- chat2db-client/src/models/workspace.ts | 2 +- .../src/pages/main/connection/index.less | 37 +- .../src/pages/main/connection/index.tsx | 434 +++++------------- chat2db-client/src/pages/main/index.tsx | 21 +- .../main/team/datasource-management/index.tsx | 4 +- .../components/NewTableList/index.less | 10 + .../components/NewTableList/index.tsx | 50 ++ .../workspace/components/TableList/index.tsx | 4 +- .../main/workspace/components/Tree/index.tsx | 284 ------------ .../components/WorkspaceHeader/index.tsx | 2 +- .../components/WorkspaceLeft/index.less | 2 +- .../components/WorkspaceLeft/index.tsx | 47 +- .../components/WorkspaceLeftHeader/index.less | 51 ++ .../components/WorkspaceLeftHeader/index.tsx | 72 +++ .../src/pages/main/workspace/index.tsx | 115 +---- chat2db-client/src/service/connection.ts | 50 +- chat2db-client/src/store/connection/index.ts | 47 ++ chat2db-client/src/store/console/index.ts | 43 ++ chat2db-client/src/store/index.ts | 0 chat2db-client/src/store/workspace/common.ts | 13 + chat2db-client/src/store/workspace/index.ts | 11 +- chat2db-client/src/typings/common.ts | 1 + chat2db-client/src/typings/connection.ts | 33 +- chat2db-client/src/typings/tree.ts | 5 +- 47 files changed, 1356 insertions(+), 1326 deletions(-) rename chat2db-client/src/{pages/main/workspace/components => blocks}/Tree/TreeNodeRightClick/index.less (77%) rename chat2db-client/src/{pages/main/workspace/components => blocks}/Tree/TreeNodeRightClick/index.tsx (60%) create mode 100644 chat2db-client/src/blocks/Tree/functions/refresh.ts create mode 100644 chat2db-client/src/blocks/Tree/hooks/useTreeNodeFocus.ts rename chat2db-client/src/{pages/main/workspace/components => blocks}/Tree/index.less (71%) create mode 100644 chat2db-client/src/blocks/Tree/index.tsx create mode 100644 chat2db-client/src/blocks/Tree/screenOutMenu.ts rename chat2db-client/src/{pages/main/workspace/components => blocks}/Tree/treeConfig.tsx (81%) create mode 100644 chat2db-client/src/blocks/Tree/treeStore.ts create mode 100644 chat2db-client/src/hooks/useClickAndDoubleClick.ts create mode 100644 chat2db-client/src/hooks/useGetConnection.ts delete mode 100644 chat2db-client/src/models/connection.ts create mode 100644 chat2db-client/src/pages/main/workspace/components/NewTableList/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx delete mode 100644 chat2db-client/src/pages/main/workspace/components/Tree/index.tsx create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx create mode 100644 chat2db-client/src/store/connection/index.ts create mode 100644 chat2db-client/src/store/console/index.ts delete mode 100644 chat2db-client/src/store/index.ts create mode 100644 chat2db-client/src/store/workspace/common.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 63b4e0c24..b989422e5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,6 +33,7 @@ "ghostoy", "hljs", "iconfont", + "indexs", "jdbc", "kingbase", "macos", diff --git a/chat2db-client/src/blocks/CreateConnection/index.less b/chat2db-client/src/blocks/CreateConnection/index.less index 3e89f8ccf..270ce1a54 100644 --- a/chat2db-client/src/blocks/CreateConnection/index.less +++ b/chat2db-client/src/blocks/CreateConnection/index.less @@ -1,9 +1,15 @@ @import '../../styles/var.less'; .box { + width: 100%; + height: 100%; + overflow-y: auto; +} + +.dataBaseListBox{ display: flex; - align-items: center; justify-content: center; + align-items: center; min-height: 100%; } @@ -86,8 +92,11 @@ } .createConnections { + min-height: 100%; + display: flex; + justify-content: center; + align-items: center; background-color: var(--color-bg); - overflow-y: auto; transform: scale(0.2); transition: 0.1s ease-in-out; } @@ -95,4 +104,27 @@ .showCreateConnections { transform: scale(1); transition: transform 0.3s ease-in-out; -} \ No newline at end of file +} + +.notPermission { + display: flex; + flex-direction: column; + .notPermissionIconTips { + color: var(--color-text-tertiary); + text-align: center; + width: 340px; + } + .notPermissionIconBox, + .connectButtonBox { + display: flex; + justify-content: center; + } + .notPermissionIcon { + font-size: 200px; + color: var(--color-text-quaternary); + margin: 20px 0px; + } + .connectButton { + margin-top: 20px; + } +} diff --git a/chat2db-client/src/blocks/CreateConnection/index.tsx b/chat2db-client/src/blocks/CreateConnection/index.tsx index ee57ff13e..e6cbd6967 100644 --- a/chat2db-client/src/blocks/CreateConnection/index.tsx +++ b/chat2db-client/src/blocks/CreateConnection/index.tsx @@ -1,32 +1,33 @@ -import React, { memo, useEffect, useRef, useState } from 'react'; +import React, { memo, useEffect, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { IConnectionDetails, IDatabase } from '@/typings'; -import ConnectionEdit, { ICreateConnectionFunction } from '@/components/ConnectionEdit'; +import ConnectionEdit from '@/components/ConnectionEdit'; import { databaseTypeList } from '@/constants'; import Iconfont from '@/components/Iconfont'; +// IConnectionDetails 全部信息代表修改 +// null 展示因增列表 +// { type: string } 只有数据库类型代表新增 +type IEditConnectionDetail = IConnectionDetails | null | Pick; + interface IProps { className?: string; onSubmit?: (data: IConnectionDetails) => void; // 点击保存或修改的回调,我会把数据给你 - connectionDetail?: IConnectionDetails; // 如果你想编辑,就直接传入完成的数据就好 + connectionDetail: IEditConnectionDetail; + noPermission?: boolean; } export default memo((props) => { - const { className, onSubmit, connectionDetail } = props; - const [curConnection, setCurConnection] = useState>({}); - const createConnectionRef = useRef(); + const { className, onSubmit, connectionDetail: externalConnectionDetail } = props; + const [connectionDetail, setConnectionDetail] = useState(externalConnectionDetail); useEffect(() => { - if (connectionDetail) { - setCurConnection(connectionDetail); - } else { - setCurConnection({}); - } - }, [connectionDetail]); + setConnectionDetail(externalConnectionDetail); + }, [externalConnectionDetail]); function handleCreateConnections(database: IDatabase) { - setCurConnection({ + setConnectionDetail({ type: database.code, }); } @@ -37,47 +38,61 @@ export default memo((props) => { return (
- {curConnection && Object.keys(curConnection).length ? ( + {connectionDetail && (
- { - { - setCurConnection({}); - }} - connectionData={curConnection as any} - submit={handleSubmit} - /> - } +
- ) : ( -
- {databaseTypeList.map((t) => { - return ( -
-
-
-
- + )} + {!connectionDetail && ( +
+
+ {databaseTypeList.map((t) => { + return ( +
+
+
+
+ +
+ {t.name} +
+
+
- {t.name} -
-
-
-
- ); - })} - {Array.from({ length: 20 }).map((t, index) => { - return
; - })} + ); + })} + {Array.from({ length: 20 }).map((t, index) => { + return
; + })} +
)}
); }); + + +{/*
+
+ +
+
{i18n('connection.tips.noConnectionTips')}
+
+ +
+
*/} diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index acefa8051..9a26c7dd7 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -35,24 +35,6 @@ const SQLExecute = memo((props) => { const searchResultRef = useRef(null); const consoleRef = useRef(null); - // useEffect(() => { - // if (!doubleClickTreeNodeData) { - // return; - // } - // if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) { - // const { extraParams } = doubleClickTreeNodeData; - // const { tableName } = extraParams || {}; - // const ddl = `SELECT * FROM ${tableName};\n`; - // if (isActive) { - // setAppendValue({ text: ddl }); - // } - // } - // dispatch({ - // type: 'workspace/setDoubleClickTreeNodeData', - // payload: '', - // }); - // }, [doubleClickTreeNodeData]); - useUpdateEffect(() => { consoleRef.current?.editorRef?.setValue(data.initDDL, 'cover'); }, [data.initDDL]); diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.less similarity index 77% rename from chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less rename to chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.less index 9216f9546..8c9cd3330 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less +++ b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.less @@ -1,22 +1,19 @@ -@import '../../../../../../styles/var.less'; +@import '../../../styles/var.less'; .operationItem { display: flex; align-items: center; - .operationIcon { - } + .operationTitle { margin-left: 14px; } + :global { .ant-dropdown { z-index: 1080; } } } -.errorOperationItem{ - -} .monacoEditorBox { margin-top: -15px; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx similarity index 60% rename from chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx rename to chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx index b537af6c9..1af5641ef 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx @@ -1,27 +1,31 @@ import React, { memo, useMemo, useRef, useState } from 'react'; import { message, Modal, Input, Dropdown, notification } from 'antd'; -import classnames from 'classnames'; +// import classnames from 'classnames'; import i18n from '@/i18n'; import styles from './index.less'; -import Iconfont from '@/components/Iconfont'; -import { TreeNodeType, CreateTabIntroType, WorkspaceTabType, OperationColumn } from '@/constants'; -import { ITreeConfigItem, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; -import { ITreeNode } from '@/typings'; -import connectionServer from '@/service/connection'; -import mysqlServer from '@/service/sql'; + +// ----- components ----- import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; -import { ICurWorkspaceParams } from '@/models/workspace'; import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; +import MenuLabel from '@/components/MenuLabel'; + +// ----- constants ----- +import { TreeNodeType, OperationColumn } from '@/constants'; +import { ITreeNode } from '@/typings'; + +// ----- service ----- +import connectionServer from '@/service/connection'; +// import mysqlServer from '@/service/sql'; + +// ----- config ----- +import { ITreeConfigItem, treeConfig } from '../treeConfig'; export type IProps = { className?: string; - setIsLoading: (value: boolean) => void; + // setIsLoading: (value: boolean) => void; data: ITreeNode; - dispatch: any; - curWorkspaceParams: ICurWorkspaceParams; - children?: any; - trigger?: any; + children?: React.ReactNode; }; export interface IOperationColumnConfigItem { @@ -31,19 +35,24 @@ export interface IOperationColumnConfigItem { } function TreeNodeRightClick(props: IProps) { - const { className, data, setIsLoading, dispatch, curWorkspaceParams, children } = props; + const { data } = props; const [verifyDialog, setVerifyDialog] = useState(); const [verifyTableName, setVerifyTableName] = useState(''); - const [modalApi, modelDom] = Modal.useModal(); - const [notificationApi, notificationDom] = notification.useNotification(); + const [, modelDom] = Modal.useModal(); + const [, notificationDom] = notification.useNotification(); + // 拿出当前节点的配置 const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType]; const { getChildren, operationColumn } = treeNodeConfig; + const [monacoVerifyDialog, setMonacoVerifyDialog] = useState(false); + const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { return t.type === data.extraParams?.databaseType; })!; + const monacoEditorRef = useRef(null); - const OperationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { + + const operationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { [OperationColumn.Refresh]: () => { return { text: i18n('common.button.refresh'), @@ -67,26 +76,26 @@ function TreeNodeRightClick(props: IProps) { text: i18n('workspace.menu.ViewDDL'), icon: '\ue665', handle: () => { - mysqlServer - .exportCreateTableSql({ - ...curWorkspaceParams, - tableName: data.key, - } as any) - .then((res) => { - setMonacoVerifyDialog(true); - setTimeout(() => { - monacoEditorRef.current?.setValue(res, 'cover'); - }, 0); - }); + // mysqlServer + // .exportCreateTableSql({ + // ...curWorkspaceParams, + // tableName: data.key, + // } as any) + // .then((res) => { + // setMonacoVerifyDialog(true); + // setTimeout(() => { + // monacoEditorRef.current?.setValue(res, 'cover'); + // }, 0); + // }); }, }; }, - [OperationColumn.ShiftOut]: (data) => { + [OperationColumn.ShiftOut]: (_data) => { return { text: '移出', icon: '\ue62a', handle: () => { - connectionServer.remove({ id: +data.key }).then(() => { + connectionServer.remove({ id: +_data.key }).then(() => { treeConfig[TreeNodeType.DATA_SOURCES]?.getChildren!({} as any).then(() => { // setTreeData(res); }); @@ -94,15 +103,15 @@ function TreeNodeRightClick(props: IProps) { }, }; }, - [OperationColumn.CreateTable]: (data) => { + [OperationColumn.CreateTable]: () => { return { text: '新建表', icon: '\ue6b6', handle: () => { - const operationData = { - type: 'new', - nodeData: data, - }; + // const operationData = { + // type: 'new', + // nodeData: _data, + // }; // setOperationDataDialog(operationData) }, }; @@ -123,22 +132,22 @@ function TreeNodeRightClick(props: IProps) { }, }; }, - [OperationColumn.EditTable]: (data) => { + [OperationColumn.EditTable]: () => { return { text: i18n('workspace.menu.editTable'), icon: '\ue602', handle: () => { - dispatch({ - type: 'workspace/setCreateTabIntro', - payload: { - type: CreateTabIntroType.EditorTable, - workspaceTabType: WorkspaceTabType.EditTable, - treeNodeData: { - ...data, - name: data.key, - }, - }, - }); + // dispatch({ + // type: 'workspace/setCreateTabIntro', + // payload: { + // type: CreateTabIntroType.EditorTable, + // workspaceTabType: WorkspaceTabType.EditTable, + // treeNodeData: { + // ...data, + // name: data.key, + // }, + // }, + // }); }, }; }, @@ -173,27 +182,27 @@ function TreeNodeRightClick(props: IProps) { }; function handleTop() { - const api = data.pinned ? 'deleteTablePin' : 'addTablePin'; - mysqlServer[api]({ - ...curWorkspaceParams, - tableName: data.key, - } as any).then(() => { - dispatch({ - type: 'workspace/fetchGetCurTableList', - payload: { - ...curWorkspaceParams, - extraParams: curWorkspaceParams, - refresh: true, - }, - callback: () => { - message.success(i18n('common.text.submittedSuccessfully')); - }, - }); - }); + // const api = data.pinned ? 'deleteTablePin' : 'addTablePin'; + // mysqlServer[api]({ + // ...curWorkspaceParams, + // tableName: data.key, + // } as any).then(() => { + // dispatch({ + // type: 'workspace/fetchGetCurTableList', + // payload: { + // ...curWorkspaceParams, + // extraParams: curWorkspaceParams, + // refresh: true, + // }, + // callback: () => { + // message.success(i18n('common.text.submittedSuccessfully')); + // }, + // }); + // }); } function refresh() { - setIsLoading(true); + // setIsLoading(true); const params = { ...data.extraParams, extraParams:{ @@ -205,55 +214,51 @@ function TreeNodeRightClick(props: IProps) { data.children = res as any; }) .finally(() => { - setIsLoading(false); + // setIsLoading(false); }) } function openEditTableData() { - const payload = { - type: CreateTabIntroType.EditTableData, - workspaceTabType: WorkspaceTabType.EditTableData, - treeNodeData: { - ...data, - name: data.key, - }, - }; - dispatch({ - type: 'workspace/setCreateTabIntro', - payload, - }); + // const payload = { + // type: CreateTabIntroType.EditTableData, + // workspaceTabType: WorkspaceTabType.EditTableData, + // treeNodeData: { + // ...data, + // name: data.key, + // }, + // }; + // dispatch({ + // type: 'workspace/setCreateTabIntro', + // payload, + // }); } function handleOk() { if (verifyTableName === data.key) { - const p: any = { - ...data.extraParams, - tableName: data.key, - }; - mysqlServer.deleteTable(p).then(() => { - // notificationApi.success( - // { - // message: i18n('common.text.successfullyDelete'), - // } - // ) - message.success(i18n('common.text.successfullyDelete')); - dispatch({ - type: 'workspace/fetchGetCurTableList', - payload: { - ...curWorkspaceParams, - extraParams: curWorkspaceParams, - }, - callback: () => { - setVerifyDialog(false); - setVerifyTableName(''); - }, - }); - }); + // const p: any = { + // ...data.extraParams, + // tableName: data.key, + // }; + // mysqlServer.deleteTable(p).then(() => { + // message.success(i18n('common.text.successfullyDelete')); + // dispatch({ + // type: 'workspace/fetchGetCurTableList', + // payload: { + // ...curWorkspaceParams, + // extraParams: curWorkspaceParams, + // }, + // callback: () => { + // setVerifyDialog(false); + // setVerifyTableName(''); + // }, + // }); + // }); } else { message.error(i18n('workspace.tips.affirmDeleteTable')); } } + // 有些数据库不支持的操作,需要排除掉 function excludeSomeOperation() { const excludes = dataSourceFormConfig.baseInfo.excludes; const newOperationColumn: OperationColumn[] = []; @@ -274,15 +279,10 @@ function TreeNodeRightClick(props: IProps) { const dropdowns = useMemo(() => { if (dataSourceFormConfig) { return excludeSomeOperation().map((t, i) => { - const concrete = OperationColumnConfig[t](data); + const concrete = operationColumnConfig[t](data); return { key: i, - label: ( -
- -
{concrete.text}
-
- ), + label: , onClick: concrete.handle, }; }); @@ -294,22 +294,6 @@ function TreeNodeRightClick(props: IProps) { <> {modelDom} {notificationDom} - - {children || ( -
- -
- )} -
{ + const { treeNodeData } = props; + console.log(treeNodeData) +} diff --git a/chat2db-client/src/blocks/Tree/hooks/useTreeNodeFocus.ts b/chat2db-client/src/blocks/Tree/hooks/useTreeNodeFocus.ts new file mode 100644 index 000000000..a09f6dfe7 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/hooks/useTreeNodeFocus.ts @@ -0,0 +1,6 @@ +import { useTreeStore } from '../treeStore'; + +export const useTreeNodeFocus = (treeId) => { + const focusId = useTreeStore((state) => state.focusId); + return focusId === treeId; +} diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.less b/chat2db-client/src/blocks/Tree/index.less similarity index 71% rename from chat2db-client/src/pages/main/workspace/components/Tree/index.less rename to chat2db-client/src/blocks/Tree/index.less index 4492cef0d..027addf8c 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.less +++ b/chat2db-client/src/blocks/Tree/index.less @@ -1,7 +1,11 @@ -@import '../../../../../styles/var.less'; +@import '../../styles/var.less'; .treeBox { - padding: 0px 6px 6px; + padding: 0px 6px 10px; + box-sizing: border-box; + height: 100%; + overflow: hidden; + overflow-y: auto; } .treeNode { @@ -14,17 +18,29 @@ cursor: pointer; transition: opacity 0.05s ease-in, height 0.1s ease-in; user-select: none; - + padding-right: 6px; + &:hover { - background-color: var(--color-hover-bg); - .right { - background-color: var(--custom-primary); - color: var(--color-primary); - } - .arrows { - display: flex; + } +} + +.treeNodeFocus { + // background-color: var(--color-hover-bg); + // background-color: var(--color-primary-bg-hover); + background-color: var(--color-primary-hover); + // background-color: var(--color-primary); + .right { + // color: var(--color-primary); + color: var(--color-bg-base); + } + .indent { + &::before { + opacity: 0; } } + .type { + color: var(--color-bg-base); + } } .left { @@ -41,8 +57,7 @@ height: 100%; padding-left: 10px; border-radius: 2px; - // color: var(--color-text-secondary); - .moreBox{ + .moreBox { width: 22px; height: 100%; display: flex; @@ -55,28 +70,16 @@ flex-shrink: 0; transform: rotate(90deg); } - - &:hover { - background-color: var(--color-hover-bg); - .moreBox{ - opacity: 1; - } - - // .moreButton { - // opacity: 1; - // } - } } .arrows { flex-shrink: 0; + height: 20px; width: 20px; - height: 26px; display: flex; align-items: center; transform: rotate(0deg); transition: transform 0.2s ease-in; - display: none; } .loadingArrows { @@ -85,12 +88,12 @@ .arrowsIcon { display: inline-block; - transform: rotate(-90deg); + transform: rotate(0deg); font-size: 12px; } .rotateArrowsIcon { - transform: rotate(0deg); + transform: rotate(90deg); } .dblclickArea { @@ -117,20 +120,20 @@ width: 0; flex: 1; display: flex; - justify-content: space-between; align-items: center; } .name { .f-lines(1); + line-height: 20px; } .type { - font-size: 12px; + font-size: 11px; flex-shrink: 0; - text-align: right; - color: var(--color-primary-text); - opacity: 0.8; + line-height: 20px; + color: var(--color-text-tertiary); + margin-left: 10px; } .describe { @@ -148,7 +151,7 @@ &::before { position: absolute; top: 0; - right: 2px; + right: 3px; bottom: -4px; border-right: 1px solid var(--color-border); content: ''; @@ -165,8 +168,3 @@ transition: transform 0.2s ease-in; } } - -.loadingIcon { - display: inline-block; - animation: loading-animation 1s infinite linear; -} diff --git a/chat2db-client/src/blocks/Tree/index.tsx b/chat2db-client/src/blocks/Tree/index.tsx new file mode 100644 index 000000000..d0e1d1646 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/index.tsx @@ -0,0 +1,225 @@ +import React, { memo, useEffect, useMemo, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import Iconfont from '@/components/Iconfont'; +import { Tooltip, Dropdown } from 'antd'; +import { ITreeNode } from '@/typings'; +import { TreeNodeType, databaseMap } from '@/constants'; +import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; +import { useCommonStore } from '@/store/common'; +import LoadingGracile from '@/components/Loading/LoadingGracile'; +import { setFocusId, useTreeStore } from './treeStore'; +import { screenOutMenu } from './screenOutMenu'; +import MenuLabel from '@/components/MenuLabel'; + +interface IProps { + className?: string; + initialData: ITreeNode[] | null; +} + +interface TreeNodeIProps { + data: ITreeNode; + level: number; +} + +const Tree = memo((props: IProps) => { + const { className, initialData } = props; + const [treeData, setTreeData] = useState(null); + + useEffect(() => { + setTreeData(initialData); + }, [initialData]); + + const treeNodes = useMemo(() => { + return treeData?.map((item, index) => { + return ; + }); + }, [treeData]); + + return
{treeNodes}
; +}); + +const TreeNode = memo((props: TreeNodeIProps) => { + const { data: initData, level } = props; + const [isLoading, setIsLoading] = useState(false); + const indentArr = new Array(level).fill('indent'); + const [treeNodeData, setTreeNodeData] = useState({ + ...initData, + }); + const isFocus = useTreeStore((state) => state.focusId) === treeNodeData.key; + + // 加载数据 + function loadData() { + const treeNodeConfig: ITreeConfigItem = treeConfig[treeNodeData.pretendNodeType || treeNodeData.treeNodeType]; + setIsLoading(true); + setTreeNodeData({ + ...treeNodeData, + children: null, + }); + treeNodeConfig + .getChildren?.({ + ...treeNodeData.extraParams, + extraParams: { + ...treeNodeData.extraParams, + }, + }) + .then((res) => { + if (res.length) { + setTimeout(() => { + setTreeNodeData({ + ...treeNodeData, + children: res, + }); + setIsLoading(false); + }, 200); + } else { + // 处理树可能出现不连续的情况 + if (treeNodeConfig.next) { + treeNodeData.pretendNodeType = treeNodeConfig.next; + loadData(); + } else { + setTreeNodeData({ + ...treeNodeData, + children: [], + }); + setIsLoading(false); + } + } + }) + .catch(() => { + setIsLoading(false); + }); + } + + //展开-收起 + const handleClick = () => { + if ( + treeConfig[treeNodeData.treeNodeType] && + (treeNodeData.children === null || treeNodeData.children === undefined) + ) { + loadData(); + } else { + setTreeNodeData({ + ...treeNodeData, + children: null, + }); + } + }; + + // 找到对应的icon + const recognizeIcon = (treeNodeType: TreeNodeType) => { + if (treeNodeType === TreeNodeType.DATA_SOURCE) { + return databaseMap[treeNodeData.extraParams!.databaseType!]?.icon; + } else { + return ( + switchIcon[treeNodeType]?.[treeNodeData.children ? 'unfoldIcon' : 'icon'] || switchIcon[treeNodeType]?.icon + ); + } + }; + + function nodeDoubleClick() { + // if ( + // data.treeNodeType === TreeNodeType.TABLE || + // data.treeNodeType === TreeNodeType.FUNCTION || + // data.treeNodeType === TreeNodeType.TRIGGER || + // data.treeNodeType === TreeNodeType.VIEW || + // data.treeNodeType === TreeNodeType.PROCEDURE + // ) { + // dispatch({ + // type: 'workspace/setDoubleClickTreeNodeData', + // payload: data, + // }); + // } + // else { + // handleClick(data); + // } + } + + // 点击节点 + const handelClickTreeNode = () => { + useCommonStore.setState({ + focusedContent: (treeNodeData.key || '') as any, + }); + setFocusId(treeNodeData.key || ''); + }; + + // 递归渲染 + const treeNodes = useMemo(() => { + return treeNodeData.children?.map((item: any, index: number) => { + return ; + }); + }, [treeNodeData]); + + const treeNodeDom = useMemo(() => { + // const { dropdowns } = useTreeNodeRightClick({ data: treeNodeData }); + return ( + { + return { + ...item, + label: , + }; + }), + }} + overlayStyle={{ + zIndex: 1080, + }} + > + +
+
+ {indentArr.map((item, i) => { + return
; + })} +
+
+ {!treeNodeData.isLeaf && ( +
+ {isLoading ? ( + + ) : ( + + )} +
+ )} +
+
+ +
+
+
+ {treeNodeData.treeNodeType === TreeNodeType.COLUMN && ( +
+ {/* 转小写 */} + {treeNodeData.columnType?.toLowerCase()} +
+ )} +
+
+
+
+ + + ); + }, [isFocus, isLoading, treeNodeData]); + + return ( + <> + {treeNodeDom} + {treeNodes} + + ); +}); + +export default Tree; diff --git a/chat2db-client/src/blocks/Tree/screenOutMenu.ts b/chat2db-client/src/blocks/Tree/screenOutMenu.ts new file mode 100644 index 000000000..69e05ae64 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/screenOutMenu.ts @@ -0,0 +1,75 @@ +import { ITreeNode } from '@/typings'; +import { OperationColumn } from '@/constants'; +import i18n from '@/i18n'; + +// ----- components ----- +import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; +import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; + + +// ----- config ----- +import { ITreeConfigItem, treeConfig } from './treeConfig'; + +// 所有的方法 +// import { refreshTreeNode } from './functions/refresh' + +interface IProps { + treeNodeData: ITreeNode; + loadData: any; +} + +interface IOperationColumnConfigItem { + text: string; + icon: string; + handle: () => void; +} + +export const screenOutMenu = (props: IProps) => { + const { treeNodeData, loadData } = props; + + // 拿出当前节点的配置 + const treeNodeConfig: ITreeConfigItem = treeConfig[treeNodeData.treeNodeType]; + const { operationColumn } = treeNodeConfig; + + const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { + return t.type === treeNodeData.extraParams?.databaseType; + })!; + + const operationColumnConfig: { [key in OperationColumn]: IOperationColumnConfigItem } = { + [OperationColumn.Refresh]: { + text: i18n('common.button.refresh'), + icon: '\uec08', + handle: loadData, + }, + }; + + // 有些数据库不支持的操作,需要排除掉 + function excludeSomeOperation() { + const excludes = dataSourceFormConfig.baseInfo.excludes; + const newOperationColumn: OperationColumn[] = []; + operationColumn?.map((item: OperationColumn) => { + let flag = false; + excludes?.map((t) => { + if (item === t) { + flag = true; + } + }); + if (!flag) { + newOperationColumn.push(item); + } + }); + return newOperationColumn; + } + + return excludeSomeOperation().map((t, i) => { + const concrete = operationColumnConfig[t]; + return { + key: i, + onClick: concrete?.handle, + labelProps: { + icon:concrete?.icon, + label: concrete?.text + } + } + }); +}; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/blocks/Tree/treeConfig.tsx similarity index 81% rename from chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx rename to chat2db-client/src/blocks/Tree/treeConfig.tsx index 308fc1c42..4e2af6639 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/blocks/Tree/treeConfig.tsx @@ -1,8 +1,8 @@ -import { ITreeNode, IPagingData } from '@/typings'; +import { ITreeNode, IConnectionDetails } from '@/typings'; import { TreeNodeType, OperationColumn } from '@/constants'; import connectionService from '@/service/connection'; -import mysqlServer, { ISchemaParams, ITableParams } from '@/service/sql'; +import mysqlServer from '@/service/sql'; export type ITreeConfig = Partial<{ [key in TreeNodeType]: ITreeConfigItem }>; @@ -17,7 +17,8 @@ export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string; unfold icon: '\ue63e', }, [TreeNodeType.TABLES]: { - icon: '\ueac5', + icon: '\ueabe', + unfoldIcon: '\ueabf', }, [TreeNodeType.COLUMNS]: { icon: '\ueabe', @@ -52,31 +53,37 @@ export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string; unfold [TreeNodeType.TRIGGER]: { icon: '\ue64a', }, + [TreeNodeType.VIEWCOLUMNS]: { + icon: '\ueabe', + unfoldIcon: '\ueabf', + }, + [TreeNodeType.VIEWCOLUMN]: { + icon: '\ue647', + }, + [TreeNodeType.FUNCTIONS]: { + icon: '\ueabe', + unfoldIcon: '\ueabf', + }, + [TreeNodeType.PROCEDURES]: { + icon: '\ueabe', + unfoldIcon: '\ueabf', + }, + [TreeNodeType.TRIGGERS]: { + icon: '\ueabe', + unfoldIcon: '\ueabf', + }, + [TreeNodeType.VIEWS]: { + icon: '\ueabe', + unfoldIcon: '\ueabf', + }, }; -// export enum OperationColumn { -// Refresh = 'refresh', -// ShiftOut = 'shiftOut', -// CreateTable = 'createTable', -// CreateConsole = 'createConsole', -// DeleteTable = 'deleteTable', -// ViewDDL = 'viewDDL', -// EditSource = 'editSource', -// Top = 'top', -// EditTable = 'editTable', -// } - export interface ITreeConfigItem { icon?: string; getChildren?: ( params: any, options?: any, - ) => Promise< - | ITreeNode[] - | ({ - data: ITreeNode[]; - } & IPagingData) - >; + ) => Promise; next?: TreeNodeType; operationColumn?: OperationColumn[]; } @@ -85,16 +92,16 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.DATA_SOURCES]: { getChildren: () => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - let p = { + const p = { pageNo: 1, pageSize: 1000, }; connectionService .getList(p) .then((res) => { - const data: ITreeNode[] = res.data.map((t) => { + const data: ITreeNode[] = res.data.map((t: IConnectionDetails) => { return { - key: t.id!, + key: t.id, name: t.alias, treeNodeType: TreeNodeType.DATA_SOURCE, extraParams: { @@ -106,17 +113,22 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(data); }) - .catch((error) => { + .catch(() => { j(); }); }); }, }, + [TreeNodeType.DATA_SOURCE]: { - getChildren: (params) => { - const _extraParams = params.extraParams; - delete params.extraParams; - return new Promise((r: (value: ITreeNode[]) => void, j) => { + getChildren: (params: { + dataSourceId: number; + dataSourceName: string; + extraParams: any + }) => { + return new Promise((r, j) => { + const _extraParams = params.extraParams; + delete params.extraParams; connectionService .getDBList(params) .then((res) => { @@ -133,7 +145,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(data); }) - .catch((error) => { + .catch(() => { j(); }); }); @@ -141,10 +153,11 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { operationColumn: [OperationColumn.EditSource, OperationColumn.Refresh, OperationColumn.ShiftOut], next: TreeNodeType.DATABASE, }, + [TreeNodeType.DATABASE]: { icon: '\ue62c', getChildren: (params) => { - // const _extraParams = params.extraParams; + const _extraParams = params.extraParams; delete params.extraParams; return new Promise((r: (value: ITreeNode[], b?: any) => void, j) => { connectionService @@ -156,11 +169,15 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { name: t.name, treeNodeType: TreeNodeType.SCHEMAS, schemaName: t.name, + extraParams: { + ..._extraParams, + schemaName: t.name, + } }; }); r(data); }) - .catch((error) => { + .catch(() => { j(); }); }); @@ -168,15 +185,43 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { operationColumn: [OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh], next: TreeNodeType.SCHEMAS, }, + [TreeNodeType.SCHEMAS]: { icon: '\ue696', getChildren: (parentData: ITreeNode) => { - return new Promise((r: (value: ITreeNode[]) => void, j) => { + const {dataSourceId,databaseName,schemaName} = parentData.extraParams! + const preCode = [dataSourceId,databaseName,schemaName].join('-') + return new Promise((r: (value: ITreeNode[]) => void) => { const data = [ { - key: parentData.name + 'tables', + key: `${preCode}-tables`, name: 'tables', treeNodeType: TreeNodeType.TABLES, + extraParams: parentData.extraParams + }, + { + key: `${preCode}-views`, + name: 'view', + treeNodeType: TreeNodeType.VIEWS, + extraParams: parentData.extraParams + }, + { + key: `${preCode}-functions`, + name: 'functions', + treeNodeType: TreeNodeType.FUNCTIONS, + extraParams: parentData.extraParams + }, + { + key: `${preCode}-procedures`, + name: 'procedures', + treeNodeType: TreeNodeType.PROCEDURES, + extraParams: parentData.extraParams + }, + { + key: `${preCode}-triggers`, + name: 'triggers', + treeNodeType: TreeNodeType.TRIGGERS, + extraParams: parentData.extraParams }, ]; r(data); @@ -207,12 +252,13 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, }; }); - r({ - data: tableList, - pageNo: res.pageNo, - pageSize: res.pageSize, - total: res.total, - }); + r(tableList); + // { + // data: tableList, + // pageNo: res.pageNo, + // pageSize: res.pageSize, + // total: res.total, + // } }) .catch((error) => { j(error); @@ -225,24 +271,26 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.TABLE]: { icon: '\ue63e', getChildren: (params) => { - return new Promise((r: (value: ITreeNode[]) => void, j) => { + return new Promise((r: (value: ITreeNode[]) => void) => { + const {dataSourceId,databaseName,schemaName,tableName} = params.extraParams! + const preCode = [dataSourceId,databaseName,schemaName,tableName].join('-') const list = [ { + key: `${preCode}-columns`, name: 'columns', treeNodeType: TreeNodeType.COLUMNS, - key: 'columns', extraParams: params.extraParams, }, { + key: `${preCode}-keys`, name: 'keys', treeNodeType: TreeNodeType.KEYS, - key: 'keys', extraParams: params.extraParams, }, { + key: `${preCode}-indexs`, name: 'indexs', treeNodeType: TreeNodeType.INDEXES, - key: 'indexs', extraParams: params.extraParams, }, ]; @@ -404,7 +452,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.VIEW]: { icon: '\ue70c', getChildren: (params) => { - return new Promise((r: (value: ITreeNode[]) => void, j) => { + return new Promise((r: (value: ITreeNode[]) => void) => { const list = [ { name: 'columns', @@ -447,6 +495,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); }, }, + [TreeNodeType.VIEWCOLUMN]: { icon: '\ue647', operationColumn: [OperationColumn.CopyName], @@ -474,7 +523,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(tableList); }) - .catch((error) => { + .catch(() => { j(); }); }); @@ -505,7 +554,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(tableList); }) - .catch((error) => { + .catch(() => { j(); }); }); @@ -536,7 +585,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); r(tableList); }) - .catch((error) => { + .catch(() => { j(); }); }); diff --git a/chat2db-client/src/blocks/Tree/treeStore.ts b/chat2db-client/src/blocks/Tree/treeStore.ts new file mode 100644 index 000000000..276b524a3 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/treeStore.ts @@ -0,0 +1,21 @@ +/** + * 树的store + */ +import { create, UseBoundStore, StoreApi } from 'zustand'; +import { devtools } from 'zustand/middleware'; + +export interface ITreeStore { + focusId: number | string | null; +} + +const treeStore = { + focusId: null, +} + +export const useTreeStore: UseBoundStore> = create( + devtools(() => (treeStore)), +); + +export const setFocusId = (focusId: ITreeStore['focusId']) => { + useTreeStore.setState({ focusId }); +} diff --git a/chat2db-client/src/components/ConnectionEdit/index.less b/chat2db-client/src/components/ConnectionEdit/index.less index f6754abc0..61ab06a56 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.less +++ b/chat2db-client/src/components/ConnectionEdit/index.less @@ -1,22 +1,11 @@ @import '../../styles/var.less'; -.box { - position: relative; - min-height: 100%; -} - -.loadingContent { - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; -} - .connectionBox { width: 100%; box-sizing: border-box; flex-shrink: 0; padding: 20px 20%; + height: auto; } .title { diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index f76c5308c..d98b33a2c 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useState, Fragment, forwardRef, ForwardedRef import { i18n, isEn } from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; -import { connect } from 'umi'; import connectionService from '@/service/connection'; import { ConnectionEnvType, databaseMap } from '@/constants'; import { dataSourceFormConfigs } from './config/dataSource'; @@ -12,10 +11,11 @@ import { IConnectionDetails } from '@/typings'; import { deepClone } from '@/utils'; import { Select, Form, Input, message, Table, Button, Collapse } from 'antd'; import Iconfont from '@/components/Iconfont'; -import LoadingContent from '@/components/Loading/LoadingContent'; import LoadingGracile from '@/components/Loading/LoadingGracile'; import Driver from './components/Driver'; -import { IConnectionModelType } from '@/models/connection'; + +// ----- store ----- +import { useConnectionStore } from '@/store/connection'; const { Option } = Select; @@ -29,19 +29,18 @@ export enum submitType { interface IProps { className?: string; - closeCreateConnection: () => void; + // closeCreateConnection: () => void; connectionData: IConnectionDetails; submitCallback?: any; submit?: (data: IConnectionDetails) => void; - connectionModel: IConnectionModelType['state']; } export interface ICreateConnectionFunction { getData: () => IConnectionDetails; } -const CreateConnection = forwardRef((props: IProps, ref: ForwardedRef) => { - const { className, closeCreateConnection, submitCallback, connectionData, submit, connectionModel } = props; +const ConnectionEdit = forwardRef((props: IProps, ref: ForwardedRef) => { + const { className, submitCallback, connectionData, submit } = props; const [baseInfoForm] = Form.useForm(); const [sshForm] = Form.useForm(); const [driveData, setDriveData] = useState({}); @@ -49,22 +48,29 @@ const CreateConnection = forwardRef((props: IProps, ref: ForwardedRef { + return { + connectionEnvList: state.connectionEnvList, + }; + }); + const [envList, setEnvList] = useState<{ value: number; label: string }[]>([]); useEffect(() => { - setEnvList( - connectionEnvList?.map((t) => { - return { - value: t.id, - label: t.name, - color: t.color, - }; - }), - ); + const _envList = connectionEnvList?.map((t) => { + return { + value: t.id, + label: t.name, + color: t.color, + }; + }) + + if(_envList){ + setEnvList(_envList); + } }, [connectionEnvList]); const dataSourceFormConfigPropsMemo = useMemo(() => { @@ -85,34 +91,6 @@ const CreateConnection = forwardRef((props: IProps, ref: ForwardedRef { - if (backfillData.id) { - getConnectionDetails(backfillData.id); - } - }, [backfillData.id]); - - function getConnectionDetails(id: number) { - setLoading({ - ...loadings, - backfillDataLoading: true, - }); - connectionService - .getDetails({ id }) - .then((res) => { - if (!res) { - return; - } - setBackfillData(res); - }) - .finally(() => { - setTimeout(() => { - setLoading({ - ...loadings, - backfillDataLoading: false, - }); - }, 100); - }); - } function driverFormChange(data: any) { setDriveData(data); @@ -250,7 +228,7 @@ const CreateConnection = forwardRef((props: IProps, ref: ForwardedRef - -
-
- -
{databaseMap[backfillData.type]?.name}
-
-
- -
- -
-
- { - - } -
-
- - -
-
+
+
+ +
{databaseMap[backfillData.type]?.name}
+
+
+ +
+ +
+
+ { + + } +
+
+ {/* */} +
- +
); }); -export default connect(({ connection }: { connection: IConnectionModelType }) => ({ - connectionModel: connection, -}))(CreateConnection); +export default ConnectionEdit; interface IRenderFormProps { tab: ITabsType; diff --git a/chat2db-client/src/components/MenuLabel/index.less b/chat2db-client/src/components/MenuLabel/index.less index cbe94af91..1dce41a69 100644 --- a/chat2db-client/src/components/MenuLabel/index.less +++ b/chat2db-client/src/components/MenuLabel/index.less @@ -8,9 +8,12 @@ display: flex; align-items: center; } - .menuLabelIcon { - } - .menuLabelTitle { + // .menuLabelIcon { + // } + // .menuLabelTitle { + // } + .menuLabelIconBright{ + color: var(--color-primary); } :global { .ant-dropdown { diff --git a/chat2db-client/src/components/MenuLabel/index.tsx b/chat2db-client/src/components/MenuLabel/index.tsx index 306cbcd8d..44027100e 100644 --- a/chat2db-client/src/components/MenuLabel/index.tsx +++ b/chat2db-client/src/components/MenuLabel/index.tsx @@ -6,15 +6,23 @@ import styles from './index.less'; interface IProps { className?: string; icon?: string; + iconBright?: boolean; label: string; } export default memo((props) => { - const { className, icon, label } = props; - return
-
- {icon && } + const { className, icon, label, iconBright } = props; + return ( +
+
+ {icon && ( + + )} +
+
{label}
-
{label}
-
; + ); }); diff --git a/chat2db-client/src/constants/environment.ts b/chat2db-client/src/constants/environment.ts index e48280558..e69de29bb 100644 --- a/chat2db-client/src/constants/environment.ts +++ b/chat2db-client/src/constants/environment.ts @@ -1,4 +0,0 @@ -export enum ConnectionEnv { - DAILY = 'DAILY', - PRODUCT = 'PRODUCT', -} diff --git a/chat2db-client/src/hooks/useClickAndDoubleClick.ts b/chat2db-client/src/hooks/useClickAndDoubleClick.ts new file mode 100644 index 000000000..8faed535f --- /dev/null +++ b/chat2db-client/src/hooks/useClickAndDoubleClick.ts @@ -0,0 +1,28 @@ +import { useState, useEffect, useCallback } from 'react'; + +const useClickAndDoubleClick = (singleClickCallback, doubleClickCallback, delay = 250) => { + const [clickCount, setClickCount] = useState(0); + const [eventData, setEventData] = useState(null); + + const handleClick = useCallback((data) => { + setEventData(data); + setClickCount((prev) => prev + 1); + }, []); + + useEffect(() => { + if (clickCount === 1) { + const singleClickTimer = setTimeout(() => { + singleClickCallback(eventData); + setClickCount(0); + }, delay); + return () => clearTimeout(singleClickTimer); + } else if (clickCount === 2) { + doubleClickCallback(eventData); + setClickCount(0); + } + }, [clickCount, eventData, singleClickCallback, doubleClickCallback, delay]); + + return handleClick; +}; + +export default useClickAndDoubleClick; diff --git a/chat2db-client/src/hooks/useGetConnection.ts b/chat2db-client/src/hooks/useGetConnection.ts new file mode 100644 index 000000000..f0f4adbd4 --- /dev/null +++ b/chat2db-client/src/hooks/useGetConnection.ts @@ -0,0 +1,56 @@ +import { useEffect } from 'react'; +import connectionService from '@/service/connection'; + +import { useConnectionStore } from '@/store/connection'; +import { useWorkspaceStore } from '@/store/workspace'; + +const useGetConnection = () => { + const { setConnectionEnvList, getConnectionList } = useConnectionStore((state) => { + return { + setConnectionEnvList: state.setConnectionEnvList, + getConnectionList: state.getConnectionList, + }; + }); + + const { currentConnectionDetails, setCurrentConnectionDetails } = useWorkspaceStore((state) => { + return { + currentConnectionDetails: state.currentConnectionDetails, + setCurrentConnectionDetails: state.setCurrentConnectionDetails, + }; + }); + + + const getConnectionEnvList = () => { + connectionService.getEnvList().then((res) => { + setConnectionEnvList(res); + }); + }; + + // 获取连接列表,获取连接环境列表 + useEffect(() => { + getConnectionList().then((res) => { + // 如果连接列表为空,则设置当前连接为空 + if(res.length === 0){ + setCurrentConnectionDetails(null); + return; + } + // 如果当前连接不存在,则设置当前连接为第一个连接 + if (!currentConnectionDetails?.id) { + setCurrentConnectionDetails(res[0]); + return; + } + // 如果存在但是不在列表中,则设置当前连接为第一个连接 + const currentConnection = res.find((item) => item.id === currentConnectionDetails?.id); + if (!currentConnection) { + setCurrentConnectionDetails(res[0]); + } + }); + getConnectionEnvList(); + }, []); + + return { + getConnectionList, + }; +}; + +export default useGetConnection; diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 05834f0be..b781d81b2 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -15,6 +15,7 @@ export default { 'connection.label.sshConfiguration': 'SSH Configuration', 'connection.button.addConnection': 'Add Connection', 'connection.button.connect': 'Connect', + 'connection.button.remove': 'Remove', 'connection.message.testConnectResult': 'Test connection is {1}', 'connection.message.testSshConnection': 'Test the ssh connection', 'connection.tableHeader.name': 'Name', diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 4b68f2fcf..465852338 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -15,6 +15,7 @@ export default { 'connection.label.sshConfiguration': 'SSH', 'connection.button.addConnection': '新增连接', 'connection.button.connect': '连接', + 'connection.button.remove': '删除链接', 'connection.message.testConnectResult': '测试连接{1}', 'connection.message.testSshConnection': '测试ssh连接', 'connection.tableHeader.name': '名称', diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts deleted file mode 100644 index d95ffd9f0..000000000 --- a/chat2db-client/src/models/connection.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Effect, Reducer } from 'umi'; -import connectionService from '@/service/connection'; -import { IPageResponse, IConnectionEnv, IConnectionDetails } from '@/typings'; -import { getCurConnection } from '@/utils/localStorage'; - -/** - * 数据源相关 - 链接池、数据库、schema、表 - */ -export interface IConnectionModelState { - curConnection?: IConnectionDetails; - connectionList: IConnectionDetails[]; - connectionEnvList: IConnectionEnv[]; -} - -export interface IConnectionModelType { - namespace: 'connection'; - state: IConnectionModelState; - reducers: { - // 设置连接列表 - setConnectionList: Reducer; - setCurConnection: Reducer; - setConnectionEnvList: Reducer; - }; - effects: { - fetchConnectionList: Effect; - fetchConnectionEnvList: Effect; - }; -} - -const ConnectionModel: IConnectionModelType = { - namespace: 'connection', - state: { - curConnection: getCurConnection(), - connectionList: [], - connectionEnvList: [] - }, - reducers: { - // 设置连接列表 - setConnectionList(state, { payload }) { - return { - ...state, - connectionList: payload, - }; - }, - - // 设置当前选着的Connection - setCurConnection(state, { payload }) { - localStorage.setItem(`cur-connection`, JSON.stringify(payload)); - return { ...state, curConnection: payload }; - }, - - // 设置连接环境列表 - setConnectionEnvList(state, { payload }) { - return { - ...state, - connectionEnvList: payload, - }; - } - }, - - effects: { - *fetchConnectionList({ callback, payload }, { call, put }) { - try { - const res = (yield connectionService.getList({ - pageNo: 1, - pageSize: 999, - refresh: payload?.refresh, - })) as IPageResponse; - yield put({ - type: 'setConnectionList', - payload: res.data, - }); - if (callback && typeof callback === 'function') { - callback(res); - } - } - catch { - - } - }, - *fetchConnectionEnvList({ callback }, { call, put }) { - try { - const res = (yield connectionService.getEnvList()) as IConnectionEnv[]; - yield put({ - type: 'setConnectionEnvList', - payload: res, - }); - if (callback && typeof callback === 'function') { - callback(res); - } - } - catch { - - } - }, - }, -}; - -export default ConnectionModel; diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 47d194365..0b4e4527b 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -4,7 +4,7 @@ import historyService from '@/service/history'; import { DatabaseTypeCode, TreeNodeType } from '@/constants'; import { Effect, Reducer } from 'umi'; import { ITreeNode, IConsole, IPageResponse, ICreateTabIntro, IWorkspaceTab } from '@/typings'; -import { treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; +import { treeConfig } from '@/blocks/Tree/treeConfig'; export type ICurWorkspaceParams = { dataSourceId: number; diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 17f2efb26..082b1fd2e 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -32,15 +32,6 @@ overflow-y: auto; padding: 0px 8px; - .menu { - min-height: 100%; - background-color: var(--color-bg-medium); - } - - .menuItemIcon { - color: var(--color-primary) !important; - } - .menuItem { display: flex; justify-content: space-between; @@ -91,6 +82,11 @@ } } + .menuItemIcon { + color: var(--color-primary) !important; + } + + .menuItemActive { color: var(--color-primary); // background-color: var(--color-primary-bg); @@ -119,29 +115,6 @@ justify-content: center; align-items: center; position: relative; - .notPermission { - display: flex; - flex-direction: column; - } - - .notPermissionIconTips { - color: var(--color-text-tertiary); - text-align: center; - width: 340px; - } - .notPermissionIconBox, - .connectButtonBox { - display: flex; - justify-content: center; - } - .notPermissionIcon { - font-size: 200px; - color: var(--color-text-quaternary); - margin: 20px 0px; - } - .connectButton { - margin-top: 20px; - } } .dataBaseList { diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 82c01756c..c511a5114 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -1,238 +1,137 @@ -import React, { useEffect, useRef, useState, Fragment } from 'react'; +import React, { useRef, useState, Fragment, useEffect } from 'react'; +import { Button, Dropdown } from 'antd'; import classnames from 'classnames'; import i18n from '@/i18n'; -import ConnectionEdit from '@/components/ConnectionEdit'; -import Iconfont from '@/components/Iconfont'; -import RefreshLoadingButton from '@/components/RefreshLoadingButton'; +// import RefreshLoadingButton from '@/components/RefreshLoadingButton'; + +// ----- services ----- import connectionService from '@/service/connection'; -import { databaseMap, databaseTypeList, ConnectionKind } from '@/constants'; -import { IDatabase, IConnectionDetails, IConnectionEnv } from '@/typings'; -import { Button, Dropdown } from 'antd'; -import { connect } from 'umi'; -import { IConnectionModelType } from '@/models/connection'; -import { IWorkspaceModelType } from '@/models/workspace'; + +// ----- constants/typings ----- +import { databaseMap } from '@/constants'; +import { IConnectionDetails, IConnectionListItem } from '@/typings'; + +// ----- components ----- +import CreateConnection from '@/blocks/CreateConnection'; +import Iconfont from '@/components/Iconfont'; import FileUploadModal from '@/components/ImportConnection'; -import styles from './index.less'; +import LoadingContent from '@/components/Loading/LoadingContent'; +import MenuLabel from '@/components/MenuLabel'; -interface IMenu { - key: number; - label: string; - icon: React.ReactNode; - meta: IConnectionDetails; - env: IConnectionEnv; -} +// ----- hooks ----- +import useClickAndDoubleClick from '@/hooks/useClickAndDoubleClick'; -interface IAllMenuList { - [ConnectionKind.Private]: { - list: IMenu[]; - name: string; - loading: boolean; - }; - [ConnectionKind.Shared]: { - list: IMenu[]; - name: string; - loading: boolean; - }; -} +// ----- store ----- +import { useConnectionStore } from '@/store/connection'; -interface IProps { - connectionModel: IConnectionModelType['state']; - dispatch: any; -} +import styles from './index.less'; -function Connections(props: IProps) { - const { connectionModel, dispatch } = props; - const { connectionList } = connectionModel; +const ConnectionsPage = () => { + const { connectionList, getConnectionList } = useConnectionStore((state) => { + return { + connectionList: state.connectionList, + getConnectionList: state.getConnectionList, + }; + }); const volatileRef = useRef(); - const [curConnection, setCurConnection] = useState>({}); - const [allMenuList, setAllMenuList] = useState(); + const [connectionActiveId, setConnectionActiveId] = useState(null); const [isFileUploadModalOpen, setIsFileUploadModalOpen] = useState(false); + const [connectionDetail, setConnectionDetail] = useState(null); - useEffect(() => { - const list: IAllMenuList = { - [ConnectionKind.Private]: { - list: [], - name: i18n('connection.label.private'), - loading: false, - }, - [ConnectionKind.Shared]: { - list: [], - name: i18n('connection.label.shared'), - loading: false, - }, - }; - connectionList.forEach((t) => { - const menu = { - key: t.id, - icon: , - label: t.alias, - meta: t, - env: t.environment, - }; - if (t.kind === ConnectionKind.Shared) { - list[ConnectionKind.Shared].list.push(menu); - } else { - list[ConnectionKind.Private].list.push(menu); - } - }); - setAllMenuList(list); - }, [connectionList]); - - function handleCreateConnections(database: IDatabase) { - setCurConnection({ - type: database.code, - }); + // 处理列表单击事件 + const handleMenuItemSingleClick = (t: IConnectionListItem) => { + if (connectionActiveId !== t.id) { + setConnectionActiveId(t.id); + } } - const handleMenuItemDoubleClick = (t?: any) => { - dispatch({ - type: 'connection/setCurConnection', - payload: t.meta, - }); - - dispatch({ - type: 'mainPage/updateCurPage', - payload: 'workspace', - }); + // 处理列表双击事件 + const handleMenuItemDoubleClick = (t: IConnectionListItem) => { + setConnectionActiveId(t.id); }; - const handleEnvRefresh = (kind: ConnectionKind) => { - let p = { - pageNo: 1, - pageSize: 999, - refresh: true, - kind, - }; - if (allMenuList) { - setAllMenuList({ - ...allMenuList, - [kind]: { - ...allMenuList[kind], - loading: true, - }, + // 处理列表单击和双击事件 + const handleClickConnectionMenu = useClickAndDoubleClick(handleMenuItemSingleClick, handleMenuItemDoubleClick); + + // 切换连接的详情 + useEffect(() => { + if (!connectionActiveId) { + return; + } + setConnectionDetail(null); + connectionService + .getDetails({ id: connectionActiveId }) + .then((res) => { + setConnectionDetail(res); + }) + .catch(() => { + setConnectionActiveId(null); + }); + }, [connectionActiveId]); + + // + const createDropdownItems = (t) => { + const handelDelete = (e) => { + // 禁止冒泡到menuItem + e.domEvent?.stopPropagation?.(); + connectionService.remove({ id: t.id }).then(() => { + getConnectionList(); + if (connectionActiveId === t.id) { + setConnectionActiveId(null); + } }); } - connectionService.getList(p).then((res) => { - if (allMenuList) { - setAllMenuList({ - ...allMenuList, - [kind]: { - ...allMenuList[kind], - list: res.data.map((t) => { - return { - key: t.id, - icon: , - label: t.alias, - meta: t, - env: t.environment, - }; - }), - loading: false, - }, - }); - } - }); - }; - const renderMenu = () => { - return ( -
- {allMenuList && - Object.keys(allMenuList).map((t, i) => { - const data = allMenuList[t as ConnectionKind]; - if (data.list?.length) { - return ( - -
-
{data.name}
- handleEnvRefresh(t as ConnectionKind)} - /> -
- {(data.list || []).map((t) => { - const { key, label, icon } = t; - return ( -
{ - if (curConnection.id !== t.meta?.id) { - setCurConnection(t.meta); - } - }} - > -
- - {icon} - {/* - {t.env.shortName?.[0]} - */} - {label} -
- { - domEvent.stopPropagation(); - handleMenuItemDoubleClick(t); - }, - }, - { - key: 'Delete', - label: i18n('common.button.delete'), - onClick: ({ domEvent }) => { - // 禁止冒泡到menuItem - domEvent.stopPropagation(); - connectionService.remove({ id: key }).then(() => { - if (curConnection.id === key) { - setCurConnection({}); - } - // // 如果当前工作区正好选中了这个连接,那么就把当前工作区的记录清空 - // if (curWorkspaceParams.dataSourceId === key) { - // dispatch({ - // type: 'workspace/setCurWorkspaceParams', - // payload: {} - // }) - // dispatch({ - // type: 'connection/setCurConnection', - // payload: {} - // }) - // } - dispatch({ - type: 'connection/fetchConnectionList', - }); - }); - }, - }, - ], - }} - > -
{ - e.stopPropagation(); - }} - > - -
-
-
- ); - })} -
- ); - } - })} -
- ); + const enterWorkSpace = (e) => { + e.domEvent?.stopPropagation?.(); + handleMenuItemDoubleClick(t); + } + + return [ + { + key: 'enterWorkSpace', + label: , + onClick: enterWorkSpace, + }, + { + key: 'delete', + label: , + onClick: handelDelete + }, + ] + } + + + const renderConnectionMenuList = () => { + return connectionList?.map((t) => { + return ( + +
{handleClickConnectionMenu(t)} } + > +
+ + + {} + + {t.alias} + {/* + {t.environment.shortName} + */} +
+
+
+ ); + }); }; return ( @@ -240,100 +139,25 @@ function Connections(props: IProps) {
{i18n('connection.title.connections')}
- {renderMenu()} - {curConnection && !!Object.keys(curConnection).length && ( +
{renderConnectionMenuList()}
+ {connectionActiveId && ( )}
-
- {Object.keys(curConnection).length ? ( - (curConnection.kind === ConnectionKind.Private && curConnection.id) || !curConnection.id ? ( -
- { - { - setCurConnection({}); - }} - submitCallback={() => { - dispatch({ - type: 'connection/fetchConnectionList', - callback: (res: any) => { - setCurConnection(res.data[res.data?.length - 1]); - }, - }); - }} - /> - } -
- ) : ( -
-
- -
-
{i18n('connection.tips.noConnectionTips')}
-
- -
-
- ) - ) : ( -
- {databaseTypeList.map((t) => { - return ( -
-
-
-
- -
- {t.name} -
-
- -
-
-
- ); - })} - -
- )} -
+ + +
+ { @@ -341,16 +165,10 @@ function Connections(props: IProps) { }} onConfirm={() => { setIsFileUploadModalOpen(false); - handleEnvRefresh(ConnectionKind.Private); }} /> ); -} +}; -export default connect( - ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ - connectionModel: connection, - workspaceModel: workspace, - }), -)(Connections); +export default ConnectionsPage; diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 236a2e14c..0e08b3dcf 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -7,16 +7,18 @@ import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; +import i18n from '@/i18n'; import { findObjListValue } from '@/utils'; import { getUser, userLogout } from '@/service/user'; import { INavItem } from '@/typings/main'; import { ILoginUser, IRole } from '@/typings/user'; -import i18n from '@/i18n'; // ----- model ----- import { IMainPageType } from '@/models/mainPage'; import { IWorkspaceModelType } from '@/models/workspace'; -import { IConnectionModelType } from '@/models/connection'; + +// ----- hooks ----- +import useGetConnection from '@/hooks/useGetConnection'; // ----- block ----- import Workspace from './workspace'; @@ -66,7 +68,6 @@ const navConfig: INavItem[] = [ interface IProps { mainModel: IMainPageType['state']; workspaceModel: IWorkspaceModelType['state']; - connectionModel: IConnectionModelType['state']; dispatch: any; } @@ -76,20 +77,13 @@ function MainPage(props: IProps) { const { curPage } = mainModel; const [activeNav, setActiveNav] = useState(null); const [userInfo, setUserInfo] = useState(); + // 获取当前连接 + useGetConnection(); useEffect(() => { handleInitPage(); }, []); - useEffect(() => { - dispatch({ - type: 'connection/fetchConnectionList', - }); - dispatch({ - type: 'connection/fetchConnectionEnvList', - }); - }, []); - useEffect(() => { if (!activeNav) { return; @@ -243,14 +237,11 @@ export default connect( ({ mainPage, workspace, - connection, }: { mainPage: IMainPageType; workspace: IWorkspaceModelType; - connection: IConnectionModelType; }) => ({ mainModel: mainPage, workspaceModel: workspace, - connectionModel: connection, }), )(MainPage); diff --git a/chat2db-client/src/pages/main/team/datasource-management/index.tsx b/chat2db-client/src/pages/main/team/datasource-management/index.tsx index 815b461f5..f34acffc1 100644 --- a/chat2db-client/src/pages/main/team/datasource-management/index.tsx +++ b/chat2db-client/src/pages/main/team/datasource-management/index.tsx @@ -24,7 +24,7 @@ function DataSourceManagement() { // pageSizeOptions: ['10', '20', '30', '40'], }); const [showCreateConnection, setShowCreateConnection] = useState(false) - const connectionInfo = useRef(); + const connectionInfo = useRef(null); const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type: AffiliationType; id?: number }>({ open: false, @@ -108,7 +108,7 @@ function DataSourceManagement() { }; const handleAddDataSource = () => { - connectionInfo.current = undefined; + connectionInfo.current = null; setShowCreateConnection(true); } diff --git a/chat2db-client/src/pages/main/workspace/components/NewTableList/index.less b/chat2db-client/src/pages/main/workspace/components/NewTableList/index.less new file mode 100644 index 000000000..97aef8e5f --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/NewTableList/index.less @@ -0,0 +1,10 @@ +@import '../../../../../styles/var.less'; + +.treeContainer{ + flex: 1; + height: 0px; +} + +.operationColumn{ + display: flex; +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx b/chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx new file mode 100644 index 000000000..1efce302c --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx @@ -0,0 +1,50 @@ +import React, { memo, useEffect, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; + +import { useWorkspaceStore } from '@/store/workspace'; + +// ----- components ----- +import Iconfont from '@/components/Iconfont'; + +import Tree from '@/blocks/Tree'; +import { treeConfig } from '@/blocks/Tree/treeConfig'; +import { ITreeNode } from '@/typings'; + +interface IProps { + className?: string; +} + +export default memo((props) => { + const { className } = props; + const [treeData, setTreeData] = useState(null); + const { currentConnectionDetails } = useWorkspaceStore((state) => { + return { + currentConnectionDetails: state.currentConnectionDetails, + } + }); + + useEffect(() => { + if(!currentConnectionDetails?.id){ + return + } + treeConfig['dataSource'].getChildren?.({ + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.name, + extraParams: { + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.name, + databaseType: currentConnectionDetails.type, + } + }).then((res) => { + setTreeData(res) + }); + }, [currentConnectionDetails]); + + return
+
+ +
+ +
+}); diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx index 2f8bfefe6..231cfa5f1 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -7,8 +7,8 @@ import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; -import Tree from '../Tree'; -import { treeConfig } from '../Tree/treeConfig'; +import Tree from '../../../../../blocks/Tree'; +import { treeConfig } from '../../../../../blocks/Tree/treeConfig'; import { TreeNodeType, WorkspaceTabType, ConsoleStatus, ConsoleOpenedStatus, OperationColumn } from '@/constants'; import { approximateTreeNode } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx deleted file mode 100644 index 67cc5612e..000000000 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'umi'; -import styles from './index.less'; -import classnames from 'classnames'; -import Iconfont from '@/components/Iconfont'; -import { Tooltip } from 'antd'; -import { ITreeNode } from '@/typings'; -import { callVar, approximateTreeNode } from '@/utils'; -import { TreeNodeType, databaseMap } from '@/constants'; -import TreeNodeRightClick from './TreeNodeRightClick'; -import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; -import { IWorkspaceModelType, ICurWorkspaceParams } from '@/models/workspace'; -import { useCommonStore } from '@/store/common'; - -interface IProps { - className?: string; - initialData?: ITreeNode[]; - workspaceModel: IWorkspaceModelType['state']; - dispatch: any; -} - -interface TreeNodeIProps { - data: ITreeNode; - level: number; - show: boolean; - setTreeData: Function; - showAllChildrenPenetrate?: boolean; - curWorkspaceParams: ICurWorkspaceParams; - dispatch: any; -} - -const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace, -})); - -const Tree = dvaModel((props: IProps) => { - const { className, initialData, workspaceModel, dispatch } = props; - const [treeData, setTreeData] = useState(); - const [searchedTreeData, setSearchedTreeData] = useState(null); - - useEffect(() => { - setTreeData(initialData); - }, [initialData]); - - function filtrationDataTree(keywords: string) { - if (!keywords) { - setSearchedTreeData(null); - } else if (treeData?.length && keywords) { - setSearchedTreeData(approximateTreeNode(treeData, keywords)); - } - } - - return ( -
- {(searchedTreeData || treeData)?.map((item, index) => { - return ( - - ); - })} -
- ); -}); - -const TreeNode = (props: TreeNodeIProps) => { - const { - setTreeData, - data, - level, - show = false, - showAllChildrenPenetrate = false, - dispatch, - curWorkspaceParams, - } = props; - const [showChildren, setShowChildren] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const indentArr = new Array(level).fill('indent'); - const [openTooltipComment, setOpenTooltipComment] = useState(false); - const contentTextRef = React.useRef(null); - const { setFocusedContent } = useCommonStore((state) => { - return { - setFocusedContent: state.setFocusedContent, - }; - }); - - useEffect(() => { - if (!data.comment) { - return; - } - if (contentTextRef.current) { - const contentTextRefDom = contentTextRef.current; - contentTextRefDom.addEventListener('mouseenter', () => { - setOpenTooltipComment(true); - }); - contentTextRefDom.addEventListener('mouseleave', () => { - setOpenTooltipComment(false); - }); - } - }, [contentTextRef]); - - function loadData(data: ITreeNode) { - const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType]; - treeNodeConfig - .getChildren?.({ - ...data.extraParams, - extraParams: { - ...data.extraParams, - }, - // ...data, - // ...(data.extraParams || {}), - }) - .then((res) => { - if (res.length) { - data.children = res; - setShowChildren(true); - setIsLoading(false); - } else { - // 处理树可能出现不连续的情况 - // if (treeNodeConfig.next) { - // data.pretendNodeType = treeNodeConfig.next; - // loadData(data); - // } else { - data.children = []; - setShowChildren(true); - setIsLoading(false); - // } - } - }) - .catch(() => { - setIsLoading(false); - }); - } - - useEffect(() => { - setShowChildren(showAllChildrenPenetrate); - }, [showAllChildrenPenetrate]); - - //展开-收起 - const handleClick = (data: ITreeNode) => { - if (!showChildren && !data.children) { - setIsLoading(true); - } - - if (treeConfig[data.treeNodeType] && !data.children) { - loadData(data); - } else { - setShowChildren(!showChildren); - } - }; - - const recognizeIcon = (treeNodeType: TreeNodeType) => { - if (treeNodeType === TreeNodeType.DATA_SOURCE) { - return databaseMap[data.extraParams?.databaseType!]?.icon; - } else { - return switchIcon[treeNodeType]?.[showChildren ? 'unfoldIcon' : 'icon'] || switchIcon[treeNodeType]?.icon; - } - }; - - function nodeDoubleClick() { - if ( - data.treeNodeType === TreeNodeType.TABLE || - data.treeNodeType === TreeNodeType.FUNCTION || - data.treeNodeType === TreeNodeType.TRIGGER || - data.treeNodeType === TreeNodeType.VIEW || - data.treeNodeType === TreeNodeType.PROCEDURE - ) { - dispatch({ - type: 'workspace/setDoubleClickTreeNodeData', - payload: data, - }); - } - // else if (data.treeNodeType === TreeNodeType.TABLE) { - // dispatch({ - // type: 'workspace/setCreateTabIntro', - // payload: { - // type: CreateTabIntroType.EditTableData, - // workspaceTabType: WorkspaceTabType.EditTableData, - // treeNodeData: data, - // }, - // }); - // } - else { - handleClick(data); - } - } - - const handelClickTreeNode = () => { - setFocusedContent((data.key || '') as any); - }; - - return show ? ( - <> - - -
-
- {indentArr.map((item, i) => { - return
; - })} -
-
- {!data.isLeaf && ( -
- {isLoading ? ( -
- -
- ) : ( - - )} -
- )} -
-
- -
-
-
- {data.treeNodeType === TreeNodeType.COLUMN &&
{data.columnType}
} -
-
-
- -
-
-
- - - {data.children?.map((item: any, i: number) => { - return ( - - ); - })} - - ) : ( - <> - ); -}; - -export default Tree; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx index ba4847e78..40d169d1a 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -9,7 +9,7 @@ import { IWorkspaceModelType } from '@/models/workspace'; import { IMainPageType } from '@/models/mainPage'; import { Cascader, Spin, Modal, Tag, Divider, ConfigProvider, Input } from 'antd'; import { databaseMap, TreeNodeType, DatabaseTypeCode } from '@/constants'; -import { treeConfig } from '../Tree/treeConfig'; +import { treeConfig } from '../../../../../blocks/Tree/treeConfig'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import styles from './index.less'; import i18n from '@/i18n'; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less index 5ceea91b3..c0658c5c6 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -1,6 +1,6 @@ @import '../../../../../styles/var.less'; -.box { +.workspaceLeft { display: flex; flex-direction: column; height: 100%; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx index 1f443ab82..f5df192a9 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -1,50 +1,17 @@ import React, { memo } from 'react'; import classnames from 'classnames'; -import { connect } from 'umi'; -import { Divider } from 'antd'; -import { IConnectionModelType } from '@/models/connection'; -import { IWorkspaceModelType } from '@/models/workspace'; import styles from './index.less'; -import TableList from '../TableList'; -import SaveList from '../SaveList'; -import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; -import { IExportParams } from '@/service/sql'; -import { downloadFile } from '@/utils/file'; +import NewTableList from '../NewTableList'; +import WorkspaceLeftHeader from '../WorkspaceLeftHeader'; -interface IProps { - className?: string; - workspaceModel: IWorkspaceModelType['state']; - dispatch: any; -} - -const dvaModel = connect( - ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ - connectionModel: connection, - workspaceModel: workspace, - }), -); - -const WorkspaceLeft = memo((props) => { - const { className, workspaceModel } = props; - const { curWorkspaceParams } = workspaceModel; - - const handleExportTableStructure = async (exportType: ExportTypeEnum) => { - const params: IExportParams = { - ...curWorkspaceParams, - originalSql: '', - exportType, - exportSize: ExportSizeEnum.ALL, - }; - downloadFile(window._BaseURL + '/api/rdb/doc/export', params); - }; +const WorkspaceLeft = memo(() => { return ( -
- - - +
+ +
); }); -export default dvaModel(WorkspaceLeft); +export default WorkspaceLeft; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less new file mode 100644 index 000000000..ce6f408c9 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less @@ -0,0 +1,51 @@ +@import '../../../../../styles/var.less'; + +.splitViewLeft{ + +} + +.selectConnection { + margin: 4px 4px; + padding: 6px 8px; + border-radius: 4px; + cursor: pointer; + &:hover { + background: var(--color-hover-bg); + } +} + +.dropdownOverlay{ + max-width: 500px; + :global { + .ant-dropdown-menu-item { + padding: 4px 6px !important; + } + } +} + +.menuLabel { + display: flex; + align-items: center; + .menuLabelIconBox{ + width: 22px; + display: flex; + align-items: center; + } + .menuLabelIcon { + color: var(--color-primary); + } + .menuLabelTag{ + margin-right: 10px; + } + .menuLabelTitle { + flex: 1; + width: 0px; + .f-single-line(); + } +} + +.databaseTypeIcon { + margin-right: 10px; + font-weight: 400; + color: var(--color-primary); +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx new file mode 100644 index 000000000..1b8468886 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx @@ -0,0 +1,72 @@ +import React, { memo, useMemo } from 'react'; +import { Dropdown, Tag } from 'antd'; +import classnames from 'classnames'; +import styles from './index.less'; + +// ---- store ---- +import { useConnectionStore } from '@/store/connection'; +import { useWorkspaceStore } from '@/store/workspace'; + +// ----- components ----- +import Iconfont from '@/components/Iconfont'; + +// ----- constants/typings ----- +import { databaseMap } from '@/constants'; + + +interface IProps { + className?: string; +} + +export default memo((props) => { + const { className } = props; + const { connectionList } = useConnectionStore((state) => { + return { + connectionList: state.connectionList, + } + }); + + const { currentConnectionDetails, setCurrentConnectionDetails } = useWorkspaceStore((state) => { + return { + currentConnectionDetails: state.currentConnectionDetails, + setCurrentConnectionDetails: state.setCurrentConnectionDetails, + } + }); + + const renderConnectionLabel = (item: { + id: number; + alias: string; + type: string; + }) => { + return
+ 开发 +
+ +
+
{item.alias}
+
+ } + + const connectionItems = useMemo(() => { + return connectionList?.map((item) => { + return { + key: item.id, + label: renderConnectionLabel(item), + onClick: () => { + setCurrentConnectionDetails(item); + } + } + }) || [] + }, [connectionList, currentConnectionDetails]); + + return
+ +
+ {currentConnectionDetails && renderConnectionLabel(currentConnectionDetails)} +
+
+
+}); diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index 537dc9c75..9fdc33d92 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -1,125 +1,44 @@ -import React, { memo, useRef, useEffect } from 'react'; -import { connect } from 'umi'; +import React, { memo, useRef } from 'react'; import classnames from 'classnames'; -import { IConnectionModelType } from '@/models/connection'; -import { IWorkspaceModelType } from '@/models/workspace'; -import { ConsoleOpenedStatus } from '@/constants'; import { useWorkspaceStore } from '@/store/workspace'; import DraggableContainer from '@/components/DraggableContainer'; -import WorkspaceHeader from './components/WorkspaceHeader'; import WorkspaceLeft from './components/WorkspaceLeft'; -import WorkspaceRight from './components/WorkspaceRight'; -import LoadingContent from '@/components/Loading/LoadingContent'; +// import WorkspaceRight from './components/WorkspaceRight'; import useMonacoTheme from '@/components/Console/MonacoEditor/useMonacoTheme'; import styles from './index.less'; -interface IProps { - className?: string; - workspaceModel: IWorkspaceModelType['state']; - connectionModel: IConnectionModelType['state']; - pageLoading: any; - dispatch: any; -} - -const dvaModel = connect( - ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ - connectionModel: connection, - workspaceModel: workspace, - }), -); - -const workspacePage = memo((props) => { +const workspacePage = memo(() => { const draggableRef = useRef(); - const { workspaceModel, connectionModel, dispatch } = props; - const { curConnection } = connectionModel; - const { curWorkspaceParams } = workspaceModel; const { panelLeft, panelLeftWidth } = useWorkspaceStore((state) => { return { panelLeft: state.layout.panelLeft, panelLeftWidth: state.layout.panelLeftWidth, } }); + // 编辑器的主题 useMonacoTheme(); - const isReady = - curWorkspaceParams?.dataSourceId && - (curWorkspaceParams?.databaseName || - curWorkspaceParams?.schemaName || - (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName === null)); - - useEffect(() => { - clearData(); - }, [curConnection]); - - useEffect(() => { - if (isReady) { - getConsoleList(); - } - }, [curWorkspaceParams]); - - function clearData() { - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: [], - }); - dispatch({ - type: 'workspace/setConsoleList', - payload: [], - }); - dispatch({ - type: 'workspace/setDatabaseAndSchema', - payload: undefined, - }); - dispatch({ - type: 'workspace/setCurTableList', - payload: [], - }); - } - - function getConsoleList() { - const p = { - pageNo: 1, - pageSize: 999, - tabOpened: ConsoleOpenedStatus.IS_OPEN, - ...curWorkspaceParams, - }; - - dispatch({ - type: 'workspace/fetchGetSavedConsoleLoading', - payload: p, - callback: (res: any) => { - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: res.data, - }); - }, - }); - } - return (
- - - -
- -
-
- -
-
-
+ +
+ +
+
+ {/* */} +
+
); }); -export default dvaModel(workspacePage); +export default workspacePage; diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index 7e485401c..8a7f6f9cc 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -1,19 +1,31 @@ -import { IPageResponse, IConnectionDetails, IConnectionEnv } from '@/typings'; -import { DatabaseTypeCode, ConnectionKind } from '@/constants'; +import { IPageResponse, IConnectionDetails, IConnectionEnv, IPageParams } from '@/typings'; +import { DatabaseTypeCode } from '@/constants'; import createRequest from './base'; -export interface IGetConnectionParams { - searchKey?: string; - pageNo: number; - pageSize: number; - refresh?: boolean; - kind?: ConnectionKind; +export interface IDriverResponse { + driverConfigList: { + jdbcDriver: string; + jdbcDriverClass: string; + }[]; + defaultDriverConfig: { + jdbcDriverClass: string; + }; +} + +interface IDriverParams { + dbType: DatabaseTypeCode; +} + +interface IUploadDriver { + multipartFiles: any; + jdbcDriverClass: string; + dbType: string; } /** * 查询连接列表 */ -const getList = createRequest>( +const getList = createRequest>( '/api/connection/datasource/list', {}, ); @@ -51,26 +63,6 @@ const getSchemaList = createRequest<{ dataSourceId: number; databaseName: string { method: 'get' }, ); -export interface IDriverResponse { - driverConfigList: { - jdbcDriver: string; - jdbcDriverClass: string; - }[]; - defaultDriverConfig: { - jdbcDriverClass: string; - }; -} - -interface IDriverParams { - dbType: DatabaseTypeCode; -} - -interface IUploadDriver { - multipartFiles: any; - jdbcDriverClass: string; - dbType: string; -} - const getDriverList = createRequest('/api/jdbc/driver/list', { errorLevel: false, method: 'get', diff --git a/chat2db-client/src/store/connection/index.ts b/chat2db-client/src/store/connection/index.ts new file mode 100644 index 000000000..6c2498ceb --- /dev/null +++ b/chat2db-client/src/store/connection/index.ts @@ -0,0 +1,47 @@ +/** + * 数据源的store + */ + +import { create, UseBoundStore, StoreApi } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { IConnectionListItem, IConnectionEnv } from '@/typings/connection'; +import connectionService from '@/service/connection'; +export interface IConnectionStore { + connectionList: IConnectionListItem[] | null; + connectionEnvList: IConnectionEnv[] | null; + setConnectionList: (connectionList: IConnectionListItem[]) => void; + setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => void; + getConnectionList: () => Promise; +} + +export const connectionStore = (set): IConnectionStore => ({ + connectionList: null, + connectionEnvList: null, + setConnectionList: (connectionList: IConnectionListItem[]) => set({ connectionList }), + setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => set({ connectionEnvList }), + getConnectionList: () => { + return new Promise((resolve, reject) => { + connectionService + .getList({ + pageNo: 1, + pageSize: 1000, + refresh: true, + }) + .then((res) => { + const connectionList = res?.data || [] + set({ connectionList }); + resolve(connectionList); + }) + .catch(() => { + set({ connectionList: [] }); + reject([]); + }); + }); + }, +}); + +export const useConnectionStore: UseBoundStore> = create( + devtools((set) => ({ + ...connectionStore(set), + })), +); diff --git a/chat2db-client/src/store/console/index.ts b/chat2db-client/src/store/console/index.ts new file mode 100644 index 000000000..2f460bd9a --- /dev/null +++ b/chat2db-client/src/store/console/index.ts @@ -0,0 +1,43 @@ +/** + * 数据源的store + */ + +import { create, UseBoundStore, StoreApi } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { IConnectionListItem, IConnectionEnv } from '@/typings/connection'; +import connectionService from '@/service/connection'; + +export interface IConnectionStore { + consoleList: IConnectionListItem[] | null; + connectionEnvList: IConnectionEnv[] | null; + setConnectionList: (connectionList: IConnectionListItem[]) => void; + setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => void; + getConnectionList: () => Promise; +} + +export const connectionStore = (set): IConnectionStore => ({ + consoleList: null, + connectionEnvList: null, + setConnectionList: (connectionList: IConnectionListItem[]) => set({ connectionList }), + setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => set({ connectionEnvList }), + getConnectionList: () => { + return connectionService + .getList({ + pageNo: 1, + pageSize: 1000, + refresh: true, + }) + .then((res) => { + set({ connectionList: res?.data || [] }); + }) + .catch(() => { + set({ connectionList: [] }); + }); + }, +}); + +export const useConnectionStore: UseBoundStore> = create( + devtools((set) => ({ + ...connectionStore(set), + })), +); diff --git a/chat2db-client/src/store/index.ts b/chat2db-client/src/store/index.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/chat2db-client/src/store/workspace/common.ts b/chat2db-client/src/store/workspace/common.ts new file mode 100644 index 000000000..49a4351b3 --- /dev/null +++ b/chat2db-client/src/store/workspace/common.ts @@ -0,0 +1,13 @@ +import { IConnectionDetails } from '@/typings/connection'; + +export interface ICommonStore { + currentConnectionDetails: Partial | null; + setCurrentConnectionDetails: (connectionDetails: Partial | null) => void; +} + + +export const commonStore = (set): ICommonStore => ({ + currentConnectionDetails: null, + setCurrentConnectionDetails: (connectionDetails: ICommonStore['currentConnectionDetails']) => + set({ currentConnectionDetails: connectionDetails }), +}); diff --git a/chat2db-client/src/store/workspace/index.ts b/chat2db-client/src/store/workspace/index.ts index 7fedea62f..3e129e9b3 100644 --- a/chat2db-client/src/store/workspace/index.ts +++ b/chat2db-client/src/store/workspace/index.ts @@ -3,8 +3,9 @@ import { devtools, persist } from 'zustand/middleware'; import { configStore, IConfigStore } from './config'; import { consoleStore, IConsoleStore } from './console'; +import { commonStore, ICommonStore } from './common'; -export type IStore = IConfigStore & IConsoleStore; +export type IStore = IConfigStore & IConsoleStore & ICommonStore; export const useWorkspaceStore: UseBoundStore> = create( devtools( @@ -12,13 +13,17 @@ export const useWorkspaceStore: UseBoundStore> = create( (set) => ({ ...configStore(set), ...consoleStore(set), + ...commonStore(set), }), // persist config { name: 'workspace-store', getStorage: () => localStorage, - // 工作区的状态只保存 layout - partialize: (state: IStore) => ({ layout: state.layout }), + // 工作区的状态只保存 layout布局信息 + partialize: (state: IStore) => ({ + layout: state.layout, + currentConnectionDetails: state.currentConnectionDetails, + }), }, ), ), diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index f9f461720..d1bffce60 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -14,6 +14,7 @@ export interface IPageParams { searchKey?: string; pageNo: number; pageSize: number; + refresh?: boolean; } export interface IPagingData { diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index fde31fb0f..a9618b281 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -1,22 +1,40 @@ -import { DatabaseTypeCode, ConnectionEnv } from '@/constants'; +import { DatabaseTypeCode } from '@/constants'; +// 连接 高级配置列表的信息 export interface IConnectionExtendInfoItem { key: string; value: string; } +// 连接的环境信息 +export interface IConnectionEnv { + id: number; + name: string; + shortName: string; + color: string; +} + +// 连接列表的信息 +export interface IConnectionListItem { + id: number; + alias: string; + environment: IConnectionEnv; + type: DatabaseTypeCode; +} + + export interface IConnectionDetails { id: number; alias: string; + environment: IConnectionEnv; + type: DatabaseTypeCode; + url: string; user: string; password: string; - type: DatabaseTypeCode; ConsoleOpenedStatus: 'y' | 'n'; - EnvType: ConnectionEnv; extendInfo: IConnectionExtendInfoItem[]; environmentId: number; - environment: IConnectionEnv, ssh: any; driverConfig: { jdbcDriver: string; @@ -27,10 +45,3 @@ export interface IConnectionDetails { export type ICreateConnectionDetails = Omit -// Connected environment -export interface IConnectionEnv { - id: number; - name: string; - shortName: string; - color: string; -} diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index 8dadc3c69..fddbe14a3 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -15,9 +15,12 @@ export interface IExtraParams { export interface ITreeNode { key: string | number; name: string; + // 用展示的name + // displayName: string; treeNodeType: TreeNodeType; // 节点的类型 表、列、文件等等 + pretendNodeType?: TreeNodeType; // 伪装的节点类型,当树不连续时,需要用到 isLeaf?: boolean; // 是否为叶子节点 - children?: ITreeNode[]; + children?: ITreeNode[] | null; columnType?: string; // 列的类型 extraParams?: IExtraParams; pinned?: boolean; // 是否置顶 From 288fedef4bba3e68bf9d31a44615d96b3d827664 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 21:27:56 +0800 Subject: [PATCH 032/126] chore: Optimize action --- .github/workflows/release_test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 1bbbff05d..c10570c62 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -64,7 +64,10 @@ jobs: run: | mkdir -p chat2db-client/static/jre cp -r "$JAVA_HOME" chat2db-client/static/jre - if [ "${{ runner.os }}" != "Windows" ]; then + if [ "${{ runner.os }}" = "Windows" ]; then + cp -r ${{ env.JAVA_HOME }} chat2db-client/static/jre + else + cp -r $JAVA_HOME chat2db-client/static/jre chmod -R 777 chat2db-client/static/jre fi shell: bash From 9ea4ffb753bdd8451afe9322270b8c14c8aa137e Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 21:32:14 +0800 Subject: [PATCH 033/126] chore: Optimize action --- .github/workflows/release_test.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index c10570c62..03b59f037 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -62,12 +62,9 @@ jobs: # JRE拷贝到前端静态目录 - name: Copy JRE to static directory run: | - mkdir -p chat2db-client/static/jre - cp -r "$JAVA_HOME" chat2db-client/static/jre - if [ "${{ runner.os }}" = "Windows" ]; then - cp -r ${{ env.JAVA_HOME }} chat2db-client/static/jre - else - cp -r $JAVA_HOME chat2db-client/static/jre + mkdir -p chat2db-client/static + cp -r "$JAVA_HOME"/jre chat2db-client/static/jre + if [ "${{ runner.os }}" != "Windows" ]; then chmod -R 777 chat2db-client/static/jre fi shell: bash From e7509cb1049397c8ccc9ece3f5308d0d83373d02 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 21:33:49 +0800 Subject: [PATCH 034/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 03b59f037..022665c8b 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -63,7 +63,7 @@ jobs: - name: Copy JRE to static directory run: | mkdir -p chat2db-client/static - cp -r "$JAVA_HOME"/jre chat2db-client/static/jre + cp -r "$JAVA_HOME"/ chat2db-client/static/jre if [ "${{ runner.os }}" != "Windows" ]; then chmod -R 777 chat2db-client/static/jre fi From 5edbc85a1e79444b24fdddb7971b25706cbb4bd4 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 21:34:45 +0800 Subject: [PATCH 035/126] chore: Optimize action --- .github/workflows/release_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 022665c8b..7a9659b34 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -75,7 +75,7 @@ jobs: - if: ${{ runner.os == 'Linux' }} name: Delete File on Linux run: | - cd chat2db-client/static/jre/x64/ + cd chat2db-client/static/jre/ ls -la rm -rf legal ls -la From 12c1c46bcd0030ff7ee0a67263365358bf34fd8a Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 12 Nov 2023 21:50:14 +0800 Subject: [PATCH 036/126] chore: Optimize action --- .github/workflows/release_test copy.yml | 331 ------------------------ .github/workflows/release_test.yml | 4 +- 2 files changed, 2 insertions(+), 333 deletions(-) delete mode 100644 .github/workflows/release_test copy.yml diff --git a/.github/workflows/release_test copy.yml b/.github/workflows/release_test copy.yml deleted file mode 100644 index 4dc80ee7a..000000000 --- a/.github/workflows/release_test copy.yml +++ /dev/null @@ -1,331 +0,0 @@ -# Workflow's name -name: Build Test Client - -# Workflow's trigger -# 在release_test 分支收到推送的时候触发 -on: - push: - branches: - - "release_test" - # - "release_test_2" - # - "release_test_3" - -# Workflow's jobs -# 一共需要3台电脑运行 -# windows -# macos-latest x86_64 -# macos-latest arm64 -jobs: - release: - strategy: - fail-fast: false - matrix: - include: - - os: windows-latest - - os: macos-latest - arch: x86_64 - - os: macos-latest - arch: arm64 - - os: ubuntu-latest - runs-on: ${{ matrix.os }} - - steps: - - name: Check out git repository - uses: actions/checkout@main - - # 安装jre Windows - - name: Install Jre for Windows - if: ${{ runner.os == 'Windows' }} - uses: actions/setup-java@main - with: - java-version: "17" - distribution: "temurin" - java-package: "jre" - - # 安装jre MacOS X64 - - name: Install Jre MacOS X64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} - uses: actions/setup-java@main - with: - java-version: "17" - distribution: "temurin" - java-package: "jre" - - # 安装jre MacOS arm64 - - name: Install Jre MacOS arm64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} - uses: actions/setup-java@main - with: - java-version: "17" - distribution: "temurin" - java-package: "jre" - architecture: "aarch64" - - # 安装jre Linux - - name: Install Jre for Linux - if: ${{ runner.os == 'Linux' }} - uses: actions/setup-java@main - with: - java-version: "17" - distribution: "temurin" - java-package: "jre" - - # java.security 开放tls1 Windows - - name: Enable tls1 - if: ${{ runner.os == 'Windows' }} - run: | - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" - - # java.security 开放tls1 macOS - - name: Enable tls1 - if: ${{ runner.os == 'macOS' }} - run: | - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" $JAVA_HOME/conf/security/java.security - - # java.security 开放tls1 Linux - - name: Enable tls1 - if: ${{ runner.os == 'Linux' }} - run: | - sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" ${{ env.JAVA_HOME }}/conf/security/java.security - - # 复制jre Windows - - name: Copy Jre for Windows - if: ${{ runner.os == 'Windows' }} - run: | - mkdir chat2db-client/static - cp -r "${{ env.JAVA_HOME }}" chat2db-client/static/jre - - # 复制jre macOS - - name: Copy Jre for macOS - if: ${{ runner.os == 'macOS' }} - run: | - mkdir chat2db-client/static - cp -r $JAVA_HOME chat2db-client/static/jre - chmod -R 777 chat2db-client/static/jre/ - - # 复制jre Linux - - name: Copy Jre for Linux - if: ${{ runner.os == 'Linux' }} - run: | - mkdir chat2db-client/static - cp -r $JAVA_HOME chat2db-client/static/jre - chmod -R 777 chat2db-client/static/jre/ - - # 安装node - - name: Install Node.js - uses: actions/setup-node@main - with: - node-version: 16 - cache: "yarn" - cache-dependency-path: chat2db-client/yarn.lock - - # 安装java - - name: Install Java and Maven - uses: actions/setup-java@main - with: - java-version: "17" - distribution: "temurin" - cache: "maven" - - # 构建静态文件信息 - - name: Yarn install & build & copy - run: | - cd chat2db-client - yarn install - yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 - cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front - cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ - - # 编译服务端java版本 - - name: Build Java - run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml - - # touch versions - - name: touch versions - run: | - cd chat2db-client - mkdir versions - mkdir versions/99.0.${{ github.run_id }} - mkdir versions/99.0.${{ github.run_id }}/static - touch version - echo -n 99.0.${{ github.run_id }} > version - cp -r version ./versions/ - - # 复制服务端java 到指定位置 - - name: Copy App - run: | - cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ - # cp -r chat2db-server/chat2db-server-start/target/lib chat2db-client/versions/99.0.${{ github.run_id }}/static/lib - - - name: Prepare Build Electron - run: | - cd chat2db-client - yarn run build:web:desktop --app_version=99.0.${{ github.run_id }} --app_port=10822 - cp -r dist ./versions/99.0.${{ github.run_id }}/ - rm -r dist - - # windows - - name: Build/release Electron app for Windows - if: ${{ runner.os == 'Windows' }} - uses: samuelmeuli/action-electron-builder@v1 - with: - package_root: "chat2db-client/" - GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - mac_certs: ${{ secrets.mac_certs }} - mac_certs_password: ${{ secrets.mac_certs_password }} - skip_build: true - args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.win.publisherName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --win --x64" - - # macos x86_64 - - name: Build/release Electron app for MacOS X64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} - uses: samuelmeuli/action-electron-builder@v1 - with: - package_root: "chat2db-client/" - GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - mac_certs: ${{ secrets.mac_certs }} - mac_certs_password: ${{ secrets.mac_certs_password }} - skip_build: true - args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --mac --x64" - - # x86_64 notarization - - name: Notarization x86_64 App - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} - run: | - xcrun notarytool store-credentials "Chat2DB" --apple-id "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --team-id "${{secrets.MAC_TEAM_ID}}" - xcrun notarytool submit chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg --keychain-profile "Chat2DB" - - # macos arm64 - - name: Build/release Electron app for MacOS arm64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} - uses: samuelmeuli/action-electron-builder@v1 - with: - package_root: "chat2db-client/" - GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - mac_certs: ${{ secrets.mac_certs }} - mac_certs_password: ${{ secrets.mac_certs_password }} - skip_build: true - args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --mac --arm64" - - # arm notarization - - name: Notarization arm64 App - if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} - run: | - xcrun notarytool store-credentials "Chat2DB" --apple-id "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --team-id "${{secrets.MAC_TEAM_ID}}" - xcrun notarytool submit chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg --keychain-profile "Chat2DB" - - # Linux - - name: Delete File - if: ${{ runner.os == 'Linux' }} - run: | - cd chat2db-client/static/jre/ - ls -la - rm -rf legal - ls -la - - - name: Build/release Electron app for Linux - if: ${{ runner.os == 'Linux' }} - uses: samuelmeuli/action-electron-builder@v1 - with: - package_root: "chat2db-client/" - GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - skip_build: true - args: "-c.appId=com.chat2db.test -c.productName=test -c.nsis.shortcutName=test -c.extraMetadata.version=99.1.${{ github.run_id }} --linux" - - # 准备要需要的数据 Windows - - name: Prepare upload for Windows - if: runner.os == 'Windows' - run: | - mkdir oss_temp_file - cp -r chat2db-client/release/*Setup*.exe ./oss_temp_file - - # 准备要需要的数据 MacOS x86_64 - - name: Prepare upload for MacOS x86_64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} - run: | - mkdir oss_temp_file - cp chat2db-client/versions/99.0.${{ github.run_id }}/static/chat2db-server-start.jar ./oss_temp_file - cp -r chat2db-client/release/*.dmg ./oss_temp_file - cp -r chat2db-client/versions/99.0.${{ github.run_id }}/dist ./oss_temp_file/dist - cd chat2db-client/versions/99.0.${{ github.run_id }}/ && zip -r 99.0.${{ github.run_id }}.zip ./ - cp -r 99.0.${{ github.run_id }}.zip ../../../oss_temp_file - cd static/ && zip -r chat2db-server-start.zip ./ - cp -r chat2db-server-start.zip ../../../../oss_temp_file - - # 准备要需要的数据 MacOS arm64 - - name: Prepare upload for MacOS arm64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} - run: | - mkdir oss_temp_file - cp -r chat2db-client/release/*.dmg ./oss_temp_file - - # 准备要需要的数据 Linux - - name: Prepare upload for Linux - if: runner.os == 'Linux' - run: | - mkdir oss_temp_file - cp -r chat2db-client/release/*.AppImage ./oss_temp_file - - # 把文件上传到OSS 方便下载 - - name: Set up oss utils - uses: yizhoumo/setup-ossutil@v1 - with: - endpoint: "oss-accelerate.aliyuncs.com" - access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} - access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} - ossutil-version: "1.7.16" - - name: Upload to oss - run: | - ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.0.${{ github.run_id }}/ - - # 构建完成通知 - - name: Send dingtalk message for Windows - if: ${{ runner.os == 'Windows' }} - uses: ghostoy/dingtalk-action@master - with: - webhook: ${{ secrets.DINGTALK_WEBHOOK }} - msgtype: markdown - content: | - { - "title": "Windows-test-打包完成通知", - "text": "# Windows-test-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe) " - } - - # 构建完成通知 - - name: Send dingtalk message for MacOS x86_64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} - uses: ghostoy/dingtalk-action@master - with: - webhook: ${{ secrets.DINGTALK_WEBHOOK }} - msgtype: markdown - content: | - { - "title": "MacOS-x86_64-test-构建完成通知", - "text": "# MacOS-x86_64-test-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg) \n ### jar包下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip) " - } - - # 构建完成通知 - - name: Send dingtalk message for MacOS arm64 - if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} - uses: ghostoy/dingtalk-action@master - with: - webhook: ${{ secrets.DINGTALK_WEBHOOK }} - msgtype: markdown - content: | - { - "title": "MacOS-arm64-test-构建完成通知", - "text": "# MacOS-arm64-test-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg) " - } - - # 构建完成通知 - - name: Send dingtalk message for Linux - if: ${{ runner.os == 'Linux' }} - uses: ghostoy/dingtalk-action@master - with: - webhook: ${{ secrets.DINGTALK_WEBHOOK }} - msgtype: markdown - content: | - { - "title": "Linux-test-打包完成通知", - "text": "# Linux-test-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage) " - } diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 7a9659b34..bb90ad3be 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -2,8 +2,8 @@ name: Build Test Client on: push: - # branches: - # - "release_test" + branches: + - "release_test" jobs: release: From ff06c3158a85f2dd86200bd660b444230dd6765a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 12 Nov 2023 22:12:02 +0800 Subject: [PATCH 037/126] refactor-workspace --- .../blocks/Tree/TreeNodeRightClick/index.less | 15 -- .../blocks/Tree/TreeNodeRightClick/index.tsx | 1 - chat2db-client/src/blocks/Tree/index.tsx | 24 ++-- .../src/blocks/Tree/rightClickMenu.ts | 136 ++++++++++++++++++ .../src/blocks/Tree/screenOutMenu.ts | 75 ---------- chat2db-client/src/blocks/Tree/treeConfig.tsx | 2 +- chat2db-client/src/constants/tree.ts | 2 +- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/en-us/workspace.ts | 1 + chat2db-client/src/i18n/zh-cn/common.ts | 1 + chat2db-client/src/i18n/zh-cn/workspace.ts | 1 + 11 files changed, 156 insertions(+), 103 deletions(-) create mode 100644 chat2db-client/src/blocks/Tree/rightClickMenu.ts delete mode 100644 chat2db-client/src/blocks/Tree/screenOutMenu.ts diff --git a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.less b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.less index 8c9cd3330..aa09fc090 100644 --- a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.less +++ b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.less @@ -1,20 +1,5 @@ @import '../../../styles/var.less'; -.operationItem { - display: flex; - align-items: center; - - .operationTitle { - margin-left: 14px; - } - - :global { - .ant-dropdown { - z-index: 1080; - } - } -} - .monacoEditorBox { margin-top: -15px; height: 60vh; diff --git a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx index 1af5641ef..cd52e1ccc 100644 --- a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx @@ -49,7 +49,6 @@ function TreeNodeRightClick(props: IProps) { const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { return t.type === data.extraParams?.databaseType; })!; - const monacoEditorRef = useRef(null); const operationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { diff --git a/chat2db-client/src/blocks/Tree/index.tsx b/chat2db-client/src/blocks/Tree/index.tsx index d0e1d1646..4e62eb912 100644 --- a/chat2db-client/src/blocks/Tree/index.tsx +++ b/chat2db-client/src/blocks/Tree/index.tsx @@ -9,7 +9,7 @@ import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; import { useCommonStore } from '@/store/common'; import LoadingGracile from '@/components/Loading/LoadingGracile'; import { setFocusId, useTreeStore } from './treeStore'; -import { screenOutMenu } from './screenOutMenu'; +import { getRightClickMenu } from './rightClickMenu'; import MenuLabel from '@/components/MenuLabel'; interface IProps { @@ -49,7 +49,7 @@ const TreeNode = memo((props: TreeNodeIProps) => { const isFocus = useTreeStore((state) => state.focusId) === treeNodeData.key; // 加载数据 - function loadData() { + function loadData(_props?: { refresh: boolean }) { const treeNodeConfig: ITreeConfigItem = treeConfig[treeNodeData.pretendNodeType || treeNodeData.treeNodeType]; setIsLoading(true); setTreeNodeData({ @@ -62,6 +62,7 @@ const TreeNode = memo((props: TreeNodeIProps) => { extraParams: { ...treeNodeData.extraParams, }, + refresh: _props?.refresh || false, }) .then((res) => { if (res.length) { @@ -138,7 +139,7 @@ const TreeNode = memo((props: TreeNodeIProps) => { // 点击节点 const handelClickTreeNode = () => { useCommonStore.setState({ - focusedContent: (treeNodeData.key || '') as any, + focusedContent: (treeNodeData.name || '') as any, }); setFocusId(treeNodeData.key || ''); }; @@ -151,17 +152,19 @@ const TreeNode = memo((props: TreeNodeIProps) => { }, [treeNodeData]); const treeNodeDom = useMemo(() => { - // const { dropdowns } = useTreeNodeRightClick({ data: treeNodeData }); + const dropdownsItems = getRightClickMenu({ treeNodeData, loadData }).map((item) => { + return { + ...item, + label: , + }; + }); + return ( { - return { - ...item, - label: , - }; - }), + items: dropdownsItems || [], + style: dropdownsItems ? {} : { display: 'none' }, // 有菜单项才显示 }} overlayStyle={{ zIndex: 1080, @@ -171,6 +174,7 @@ const TreeNode = memo((props: TreeNodeIProps) => {
diff --git a/chat2db-client/src/blocks/Tree/rightClickMenu.ts b/chat2db-client/src/blocks/Tree/rightClickMenu.ts new file mode 100644 index 000000000..274060857 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/rightClickMenu.ts @@ -0,0 +1,136 @@ +import { ITreeNode } from '@/typings'; +import { OperationColumn } from '@/constants'; +import i18n from '@/i18n'; + +// ----- components ----- +import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; +import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; + +// ----- config ----- +import { ITreeConfigItem, treeConfig } from './treeConfig'; + +// 所有的方法 +// import { refreshTreeNode } from './functions/refresh' + +interface IProps { + treeNodeData: ITreeNode; + loadData: any; +} + +interface IOperationColumnConfigItem { + text: string; + icon: string; + handle: () => void; +} + +export const getRightClickMenu = (props: IProps) => { + const { treeNodeData, loadData } = props; + + // 拿出当前节点的配置 + const treeNodeConfig: ITreeConfigItem = treeConfig[treeNodeData.treeNodeType]; + const { operationColumn } = treeNodeConfig; + + const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { + return t.type === treeNodeData.extraParams?.databaseType; + })!; + + const operationColumnConfig: { [key in string]: IOperationColumnConfigItem } = { + // 刷新 + [OperationColumn.Refresh]: { + text: i18n('common.button.refresh'), + icon: '\uec08', + handle: () =>{ + loadData({ + refresh: true + }) + } + }, + // 创建表 + [OperationColumn.CreateTable]: { + text: i18n('editTable.button.createTable'), + icon: '\ue792', + handle: () => { + console.log('创建表'); + // const operationData = { + // type: 'new', + // nodeData: _data, + // }; + // setOperationDataDialog(operationData) + }, + }, + // 创建console + [OperationColumn.CreateConsole]: { + text: i18n('workspace.menu.queryConsole'), + icon: '\ue619', + handle: () => { + console.log('创建console'); + }, + }, + // 删除表 + [OperationColumn.DeleteTable]: { + text: i18n('workspace.menu.deleteTable'), + icon: '\ue6a7', + handle: () => { + // setVerifyDialog(true); + }, + }, + // 查看ddl + [OperationColumn.ViewDDL]: { + text: i18n('workspace.menu.ViewDDL'), + icon: '\ue665', + handle: () => { + // + }, + }, + // 置顶 + [OperationColumn.Top]: { + text: treeNodeData.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), + icon: treeNodeData.pinned ? '\ue61d' : '\ue627', + handle: () => {}, + }, + // 编辑表 + [OperationColumn.EditTable]: { + text: i18n('workspace.menu.editTable'), + icon: '\ue602', + handle: () => {}, + }, + // 复制名称 + [OperationColumn.CopyName]: { + text: i18n('common.button.copyName'), + icon: '\uec7a', + handle: () => { + navigator.clipboard.writeText(treeNodeData.name); + }, + }, + }; + + // 有些数据库不支持的操作,需要排除掉 + function excludeSomeOperation() { + const excludes = dataSourceFormConfig.baseInfo.excludes; + const newOperationColumn: OperationColumn[] = []; + operationColumn?.map((item: OperationColumn) => { + let flag = false; + excludes?.map((t) => { + if (item === t) { + flag = true; + } + }); + if (!flag) { + newOperationColumn.push(item); + } + }); + return newOperationColumn; + } + + return excludeSomeOperation().map((t, i) => { + const concrete = operationColumnConfig[t]; + return { + key: i, + onClick: concrete?.handle, + labelProps: { + icon: concrete?.icon, + label: concrete?.text, + }, + }; + }); +}; diff --git a/chat2db-client/src/blocks/Tree/screenOutMenu.ts b/chat2db-client/src/blocks/Tree/screenOutMenu.ts deleted file mode 100644 index 69e05ae64..000000000 --- a/chat2db-client/src/blocks/Tree/screenOutMenu.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ITreeNode } from '@/typings'; -import { OperationColumn } from '@/constants'; -import i18n from '@/i18n'; - -// ----- components ----- -import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; -import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; - - -// ----- config ----- -import { ITreeConfigItem, treeConfig } from './treeConfig'; - -// 所有的方法 -// import { refreshTreeNode } from './functions/refresh' - -interface IProps { - treeNodeData: ITreeNode; - loadData: any; -} - -interface IOperationColumnConfigItem { - text: string; - icon: string; - handle: () => void; -} - -export const screenOutMenu = (props: IProps) => { - const { treeNodeData, loadData } = props; - - // 拿出当前节点的配置 - const treeNodeConfig: ITreeConfigItem = treeConfig[treeNodeData.treeNodeType]; - const { operationColumn } = treeNodeConfig; - - const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { - return t.type === treeNodeData.extraParams?.databaseType; - })!; - - const operationColumnConfig: { [key in OperationColumn]: IOperationColumnConfigItem } = { - [OperationColumn.Refresh]: { - text: i18n('common.button.refresh'), - icon: '\uec08', - handle: loadData, - }, - }; - - // 有些数据库不支持的操作,需要排除掉 - function excludeSomeOperation() { - const excludes = dataSourceFormConfig.baseInfo.excludes; - const newOperationColumn: OperationColumn[] = []; - operationColumn?.map((item: OperationColumn) => { - let flag = false; - excludes?.map((t) => { - if (item === t) { - flag = true; - } - }); - if (!flag) { - newOperationColumn.push(item); - } - }); - return newOperationColumn; - } - - return excludeSomeOperation().map((t, i) => { - const concrete = operationColumnConfig[t]; - return { - key: i, - onClick: concrete?.handle, - labelProps: { - icon:concrete?.icon, - label: concrete?.text - } - } - }); -}; diff --git a/chat2db-client/src/blocks/Tree/treeConfig.tsx b/chat2db-client/src/blocks/Tree/treeConfig.tsx index 4e2af6639..0044e6b61 100644 --- a/chat2db-client/src/blocks/Tree/treeConfig.tsx +++ b/chat2db-client/src/blocks/Tree/treeConfig.tsx @@ -182,7 +182,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); }); }, - operationColumn: [OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh], + operationColumn: [OperationColumn.CreateConsole, OperationColumn.CopyName, OperationColumn.Refresh,], next: TreeNodeType.SCHEMAS, }, diff --git a/chat2db-client/src/constants/tree.ts b/chat2db-client/src/constants/tree.ts index 5e9f87e03..bfd5dcd72 100644 --- a/chat2db-client/src/constants/tree.ts +++ b/chat2db-client/src/constants/tree.ts @@ -30,7 +30,7 @@ export enum OperationColumn { CreateTable = 'createTable', //创建表 CreateConsole = 'createConsole', // 新建console DeleteTable = 'deleteTable', // 删除表 - ViewDDL = 'viewDDL', // 导出ddl + ViewDDL = 'viewDDL', // 查看ddl EditSource = 'editSource', // 编辑数据源 Top = 'top', // 置顶 EditTable = 'editTable', // 编辑表 diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 2216f71e9..4c18366fc 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -45,6 +45,7 @@ export default { 'common.text.table': 'Table', 'common.tips.saveSuccessfully': 'Save Successfully', 'common.button.copy': 'Copy', + 'common.button.copyName': 'Copy name', 'common.button.copySuccessfully': 'Copy Successfully', 'common.button.createConsole': 'Create Console', 'common.button.exportWord': 'Export to Word', diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 1ccc265f1..a82e782f6 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -9,6 +9,7 @@ export default { 'workspace.menu.pin': 'Pin', 'workspace.menu.unPin': 'Unpin', 'workspace.menu.editTableData': 'Edit Table Data', + 'workspace.menu.queryConsole': 'Query console', 'workspace.menu.deleteTablePlaceHolder': 'Please enter the name of the table you want to delete', 'workspace.tips.affirmDeleteTable': 'The table name you entered is not the same as the table name you want to delete, please confirm again', diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 3d59bf3c6..7f55a9077 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -45,6 +45,7 @@ export default { 'common.text.table': '表', 'common.tips.saveSuccessfully': '保存成功', 'common.button.copy': '复制', + 'common.button.copyName': '复制名称', 'common.button.copySuccessfully': '复制成功', 'common.button.createConsole': '新建控制台', 'common.button.exportWord': '导出到Word', diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 07c4f1591..bc5921d62 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -9,6 +9,7 @@ export default { 'workspace.menu.pin': '置顶', 'workspace.menu.unPin': '取消置顶', 'workspace.menu.editTableData': '编辑表数据', + 'workspace.menu.queryConsole': '新建查询', 'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名', 'workspace.tips.affirmDeleteTable': '输入的表名与要删除的表名不一致,请再次确认', 'workspace.table.total': '总数', From eed78015dce3e7c8c02212ed923062a07a384677 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 12 Nov 2023 22:25:51 +0800 Subject: [PATCH 038/126] Driver configuration is configured through json, Support driver attribute configuration. --- .../chat2db-clickhouse/pom.xml | 14 +++ .../plugin/clickhouse/ClickHousePlugin.java | 4 +- .../clickhouse/builder/DBConfigBuilder.java | 23 ---- .../chat2db/plugin/clickhouse/clickhouse.json | 16 +++ .../chat2db-plugins/chat2db-db2/pom.xml | 15 ++- .../java/ai/chat2db/plugin/db2/DB2Plugin.java | 5 +- .../plugin/db2/builder/DBConfigBuilder.java | 22 ---- .../main/java/ai/chat2db/plugin/db2/db2.json | 16 +++ .../chat2db-plugins/chat2db-dm/pom.xml | 15 ++- .../java/ai/chat2db/plugin/dm/DMPlugin.java | 5 +- .../plugin/dm/builder/DBConfigBuilder.java | 23 ---- .../main/java/ai/chat2db/plugin/dm/dm.json | 16 +++ .../chat2db-plugins/chat2db-h2/pom.xml | 15 ++- .../java/ai/chat2db/plugin/h2/H2Plugin.java | 5 +- .../plugin/h2/builder/DBConfigBuilder.java | 23 ---- .../main/java/ai/chat2db/plugin/h2/h2.json | 16 +++ .../chat2db-plugins/chat2db-hive/pom.xml | 14 ++- .../ai/chat2db/plugin/hive/HivePlugin.java | 4 +- .../plugin/hive/builder/DBConfigBuilder.java | 20 --- .../java/ai/chat2db/plugin/hive/hive.json | 16 +++ .../chat2db-plugins/chat2db-kingbase/pom.xml | 15 ++- .../plugin/kingbase/KingBasePlugin.java | 4 +- .../kingbase/builder/DBConfigBuilder.java | 28 ----- .../ai/chat2db/plugin/kingbase/kingbase.json | 26 ++++ .../chat2db-plugins/chat2db-mariadb/pom.xml | 15 ++- .../chat2db/plugin/mariadb/MariaDBPlugin.java | 4 +- .../mariadb/builder/DBConfigBuilder.java | 20 --- .../ai/chat2db/plugin/mariadb/mariadb.json | 16 +++ .../chat2db-plugins/chat2db-mongodb/pom.xml | 15 ++- .../chat2db/plugin/mongodb/MongodbPlugin.java | 5 +- .../mongodb/builder/DBConfigBuilder.java | 20 --- .../ai/chat2db/plugin/mongodb/mongodb.json | 16 +++ .../chat2db-plugins/chat2db-mysql/pom.xml | 14 +++ .../ai/chat2db/plugin/mysql/MysqlPlugin.java | 4 +- .../plugin/mysql/builder/DBConfigBuilder.java | 29 ----- .../java/ai/chat2db/plugin/mysql/mysql.json | 26 ++++ .../chat2db-plugins/chat2db-oceanbase/pom.xml | 15 ++- .../plugin/oceanbase/OceanBasePlugin.java | 5 +- .../oceanbase/builder/DBConfigBuilder.java | 20 --- .../chat2db/plugin/oceanbase/oceanbase.json | 16 +++ .../chat2db-plugins/chat2db-oracle/pom.xml | 14 +++ .../chat2db/plugin/oracle/OraclePlugin.java | 5 +- .../oracle/builder/DBConfigBuilder.java | 23 ---- .../java/ai/chat2db/plugin/oracle/oracle.json | 17 +++ .../chat2db-postgresql/pom.xml | 15 ++- .../plugin/postgresql/PostgreSQLPlugin.java | 4 +- .../postgresql/builder/DBConfigBuilder.java | 21 ---- .../java/ai/chat2db/plugin/postgresql/pg.json | 16 +++ .../chat2db-plugins/chat2db-presto/pom.xml | 15 ++- .../chat2db/plugin/presto/PrestoPlugin.java | 4 +- .../presto/builder/DBConfigBuilder.java | 20 --- .../java/ai/chat2db/plugin/presto/presto.json | 16 +++ .../chat2db-plugins/chat2db-redis/pom.xml | 15 ++- .../ai/chat2db/plugin/redis/RedisPlugin.java | 5 +- .../plugin/redis/builder/DBConfigBuilder.java | 20 --- .../java/ai/chat2db/plugin/redis/redis.json | 16 +++ .../chat2db-plugins/chat2db-sqlite/pom.xml | 14 +++ .../chat2db/plugin/sqlite/SqlitePlugin.java | 4 +- .../sqlite/builder/DBConfigBuilder.java | 21 ---- .../java/ai/chat2db/plugin/sqlite/sqlite.json | 16 +++ .../chat2db-plugins/chat2db-sqlserver/pom.xml | 15 ++- .../plugin/sqlserver/SqlServerPlugin.java | 4 +- .../builder/SqlServerDBConfigBuilder.java | 21 ---- .../chat2db/plugin/sqlserver/sqlserver.json | 16 +++ .../core/converter/DriverConfigConverter.java | 13 ++ .../core/impl/DataSourceServiceImpl.java | 4 + .../core/impl/JdbcDriverServiceImpl.java | 74 ++++++------ .../chat2db/server/test/temp/DriverTest.java | 15 +++ chat2db-server/chat2db-spi/pom.xml | 2 +- .../java/ai/chat2db/spi/config/DBConfig.java | 73 ++++++++++- .../ai/chat2db/spi/config/DriverConfig.java | 22 +++- .../java/ai/chat2db/spi/model/KeyValue.java | 12 +- .../ai/chat2db/spi/sql/IDriverManager.java | 39 ++++-- .../java/ai/chat2db/spi/util/FileUtils.java | 21 ++++ .../java/ai/chat2db/spi/util/JdbcUtils.java | 114 ++++++++++++++++-- 75 files changed, 859 insertions(+), 467 deletions(-) delete mode 100644 chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/DBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json delete mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerDBConfigBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DriverConfigConverter.java create mode 100644 chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/pom.xml b/chat2db-server/chat2db-plugins/chat2db-clickhouse/pom.xml index 37a9b7986..e3edd0f6d 100644 --- a/chat2db-server/chat2db-plugins/chat2db-clickhouse/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-clickhouse/pom.xml @@ -17,4 +17,18 @@ chat2db-clickhouse + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHousePlugin.java b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHousePlugin.java index b9f801621..ecd38c250 100644 --- a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHousePlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHousePlugin.java @@ -1,16 +1,16 @@ package ai.chat2db.plugin.clickhouse; -import ai.chat2db.plugin.clickhouse.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class ClickHousePlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"clickhouse.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/DBConfigBuilder.java deleted file mode 100644 index 38448f8b2..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/builder/DBConfigBuilder.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.chat2db.plugin.clickhouse.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("ClickHouse"); - dbConfig.setDbType("CLICKHOUSE"); - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("clickhouse-jdbc-0.3.2-patch8-http.jar"); - driverConfig.setJdbcDriverClass("com.clickhouse.jdbc.ClickHouseDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/clickhouse-jdbc-0.3.2-patch8-http.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} - diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json new file mode 100644 index 000000000..f0528ea21 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json @@ -0,0 +1,16 @@ +{ + "dbType": "CLICKHOUSE", + "driverConfigList": [ + { + "url": "jdbc:clickhouse://localhost:8123/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/clickhouse-jdbc-0.3.2-patch8-http.jar" + ], + "jdbcDriver": "clickhouse-jdbc-0.3.2-patch8-http.jar", + "jdbcDriverClass": "com.clickhouse.jdbc.ClickHouseDriver" + } + ], + "name": "ClickHouse" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/pom.xml b/chat2db-server/chat2db-plugins/chat2db-db2/pom.xml index 1bf7e5efe..11afc7adf 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-db2/pom.xml @@ -17,5 +17,18 @@ chat2db-db2 - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2Plugin.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2Plugin.java index e162f9911..71e049a11 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2Plugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2Plugin.java @@ -1,15 +1,16 @@ package ai.chat2db.plugin.db2; -import ai.chat2db.plugin.db2.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class DB2Plugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"db2.json", DBConfig.class); + } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DBConfigBuilder.java deleted file mode 100644 index 8d5445a2c..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DBConfigBuilder.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.chat2db.plugin.db2.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("DB2"); - dbConfig.setDbType("DB2"); - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("db2jcc4_4.26.14.jar"); - driverConfig.setJdbcDriverClass("com.ibm.db2.jcc.DB2Driver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/db2jcc4_4.26.14.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json new file mode 100644 index 000000000..0f72b5964 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json @@ -0,0 +1,16 @@ +{ + "dbType": "DB2", + "driverConfigList": [ + { + "url": "jdbc:db2://localhost:50000/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/db2jcc4_4.26.14.jar" + ], + "jdbcDriver": "db2jcc4_4.26.14.jar", + "jdbcDriverClass": "com.ibm.db2.jcc.DB2Driver" + } + ], + "name": "DB2" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/pom.xml b/chat2db-server/chat2db-plugins/chat2db-dm/pom.xml index 228df9d3c..4e953132f 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-dm/pom.xml @@ -18,5 +18,18 @@ chat2db-dm - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMPlugin.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMPlugin.java index c26de3d05..3cbeda99f 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMPlugin.java @@ -1,15 +1,16 @@ package ai.chat2db.plugin.dm; -import ai.chat2db.plugin.dm.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class DMPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"dm.json", DBConfig.class); + } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DBConfigBuilder.java deleted file mode 100644 index ae3fabedf..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/builder/DBConfigBuilder.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.chat2db.plugin.dm.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("DM"); - dbConfig.setDbType("DM"); - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("DmJdbcDriver18-8.1.2.141.jar"); - driverConfig.setJdbcDriverClass("dm.jdbc.driver.DmDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/DmJdbcDriver18-8.1.2.141.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json new file mode 100644 index 000000000..e58e12548 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json @@ -0,0 +1,16 @@ +{ + "dbType": "DM", + "driverConfigList": [ + { + "url": "jdbc:dm://localhost:5236/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/DmJdbcDriver18-8.1.2.141.jar" + ], + "jdbcDriver": "DmJdbcDriver18-8.1.2.141.jar", + "jdbcDriverClass": "dm.jdbc.driver.DmDriver" + } + ], + "name": "DM" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/pom.xml b/chat2db-server/chat2db-plugins/chat2db-h2/pom.xml index 32dd725ce..4192b0e14 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-h2/pom.xml @@ -17,5 +17,18 @@ chat2db-h2 - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Plugin.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Plugin.java index 14fd56daf..675f8887a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Plugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Plugin.java @@ -1,16 +1,16 @@ package ai.chat2db.plugin.h2; -import ai.chat2db.plugin.h2.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.util.FileUtils; public class H2Plugin extends DefaultMetaService implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"h2.json", DBConfig.class); } @Override @@ -22,4 +22,5 @@ public MetaData getMetaData() { public DBManage getDBManage() { return new H2DBManage(); } + } diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/DBConfigBuilder.java deleted file mode 100644 index 623148abd..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/DBConfigBuilder.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.chat2db.plugin.h2.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("H2"); - dbConfig.setDbType("H2"); - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("h2-2.1.214.jar"); - driverConfig.setJdbcDriverClass("org.h2.Driver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/h2-2.1.214.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json new file mode 100644 index 000000000..552e0ef35 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json @@ -0,0 +1,16 @@ +{ + "dbType": "H2", + "driverConfigList": [ + { + "url": "jdbc:h2:tcp://localhost:9092/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/h2-2.1.214.jar" + ], + "jdbcDriver": "h2-2.1.214.jar", + "jdbcDriverClass": "org.h2.Driver" + } + ], + "name": "H2" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/pom.xml b/chat2db-server/chat2db-plugins/chat2db-hive/pom.xml index d5f4b1509..e5cc3cc82 100644 --- a/chat2db-server/chat2db-plugins/chat2db-hive/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-hive/pom.xml @@ -18,6 +18,18 @@ chat2db-hive - + + + + src/main/java + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HivePlugin.java b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HivePlugin.java index bf17d4517..43b815060 100644 --- a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HivePlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HivePlugin.java @@ -1,15 +1,15 @@ package ai.chat2db.plugin.hive; -import ai.chat2db.plugin.hive.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class HivePlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"hive.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/DBConfigBuilder.java deleted file mode 100644 index ba2b409cc..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/builder/DBConfigBuilder.java +++ /dev/null @@ -1,20 +0,0 @@ -package ai.chat2db.plugin.hive.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("Hive"); - dbConfig.setDbType("HIVE"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("hive-jdbc-3.1.2-standalone.jar"); - driverConfig.setJdbcDriverClass("org.apache.hive.jdbc.HiveDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/hive-jdbc-3.1.2-standalone.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json new file mode 100644 index 000000000..87f528c86 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json @@ -0,0 +1,16 @@ +{ + "dbType": "HIVE", + "driverConfigList": [ + { + "url": "jdbc:hive2://localhost:10000/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/hive-jdbc-3.1.2-standalone.jar" + ], + "jdbcDriver": "hive-jdbc-3.1.2-standalone.jar", + "jdbcDriverClass": "org.apache.hive.jdbc.HiveDriver" + } + ], + "name": "Hive" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/pom.xml b/chat2db-server/chat2db-plugins/chat2db-kingbase/pom.xml index 31b87ca69..262e20f0c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/pom.xml @@ -18,5 +18,18 @@ chat2db-kingbase - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBasePlugin.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBasePlugin.java index 99c8ff30d..4fb2f2863 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBasePlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBasePlugin.java @@ -1,15 +1,15 @@ package ai.chat2db.plugin.kingbase; -import ai.chat2db.plugin.kingbase.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class KingBasePlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"kingbase.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/DBConfigBuilder.java deleted file mode 100644 index 191c94c82..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/builder/DBConfigBuilder.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.chat2db.plugin.kingbase.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("KingBase"); - dbConfig.setDbType("KINGBASE"); - - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("kingbase8-8.6.0.jar"); - driverConfig.setJdbcDriverClass("com.kingbase8.Driver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/kingbase8-8.6.0.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - - DriverConfig driverConfig1 = new DriverConfig(); - driverConfig1.setJdbcDriver("kingbase8-8.2.0.jar"); - driverConfig1.setJdbcDriverClass("com.kingbase8.Driver"); - driverConfig1.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/kingbase8-8.2.0.jar")); - - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig,driverConfig1)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json new file mode 100644 index 000000000..540721bc6 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json @@ -0,0 +1,26 @@ +{ + "dbType": "KINGBASE", + "driverConfigList": [ + { + "url": "jdbc:kingbase8://localhost:54321/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/kingbase8-8.6.0.jar" + ], + "jdbcDriver": "kingbase8-8.6.0.jar", + "jdbcDriverClass": "com.kingbase8.Driver" + }, + { + "url": "jdbc:kingbase8://localhost:54321/", + "custom": false, + "defaultDriver": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/kingbase8-8.2.0.jar" + ], + "jdbcDriver": "kingbase8-8.2.0.jar", + "jdbcDriverClass": "com.kingbase8.Driver" + } + ], + "name": "KingBase" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml b/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml index c34a9f660..99508bf62 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml @@ -23,5 +23,18 @@ chat2db-mariadb - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBPlugin.java b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBPlugin.java index 083d43c9a..069c76c5b 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBPlugin.java @@ -1,15 +1,15 @@ package ai.chat2db.plugin.mariadb; -import ai.chat2db.plugin.mariadb.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class MariaDBPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"mariadb.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/builder/DBConfigBuilder.java deleted file mode 100644 index 88c0cfc5e..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/builder/DBConfigBuilder.java +++ /dev/null @@ -1,20 +0,0 @@ -package ai.chat2db.plugin.mariadb.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("MariaDB"); - dbConfig.setDbType("MARIADB"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("mariadb-java-client-3.0.8.jar"); - driverConfig.setJdbcDriverClass("org.mariadb.jdbc.Driver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/mariadb-java-client-3.0.8.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json new file mode 100644 index 000000000..e2e33688d --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json @@ -0,0 +1,16 @@ +{ + "dbType": "MARIADB", + "driverConfigList": [ + { + "url": "jdbc:mariadb://localhost:3306/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/mariadb-java-client-3.0.8.jar" + ], + "jdbcDriver": "mariadb-java-client-3.0.8.jar", + "jdbcDriverClass": "org.mariadb.jdbc.Driver" + } + ], + "name": "MariaDB" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/pom.xml b/chat2db-server/chat2db-plugins/chat2db-mongodb/pom.xml index 70d44ba4d..8cfe62d7e 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/pom.xml @@ -18,6 +18,19 @@ chat2db-mongodb - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbPlugin.java b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbPlugin.java index 15da3322b..35ac2f24a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbPlugin.java @@ -1,15 +1,16 @@ package ai.chat2db.plugin.mongodb; -import ai.chat2db.plugin.mongodb.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class MongodbPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"mongodb.json", DBConfig.class); + } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/builder/DBConfigBuilder.java deleted file mode 100644 index e888890a1..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/builder/DBConfigBuilder.java +++ /dev/null @@ -1,20 +0,0 @@ -package ai.chat2db.plugin.mongodb.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("Mongodb"); - dbConfig.setDbType("MONGODB"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("mongo-jdbc-standalone-1.18.jar"); - driverConfig.setJdbcDriverClass("com.dbschema.MongoJdbcDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/mongo-jdbc-standalone-1.18.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json new file mode 100644 index 000000000..cb4118f99 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json @@ -0,0 +1,16 @@ +{ + "dbType": "MONGODB", + "driverConfigList": [ + { + "url": "jdbc:mongodb://localhost:27017", + "defaultDriver":true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/mongo-jdbc-standalone-1.18.jar" + ], + "jdbcDriver": "mongo-jdbc-standalone-1.18.jar", + "jdbcDriverClass": "com.dbschema.MongoJdbcDriver" + } + ], + "name": "Mongodb" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml b/chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml index 4b0bbdb27..d867bde57 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml @@ -18,4 +18,18 @@ chat2db-spi + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java index 958e13fe2..a22b85235 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java @@ -1,16 +1,16 @@ package ai.chat2db.plugin.mysql; -import ai.chat2db.plugin.mysql.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class MysqlPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"mysql.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java deleted file mode 100644 index bf6d464f3..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.chat2db.plugin.mysql.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("Mysql"); - dbConfig.setDbType("MYSQL"); - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("mysql-connector-java-8.0.30.jar"); - driverConfig.setJdbcDriverClass("com.mysql.cj.jdbc.Driver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/mysql-connector-java-8.0.30.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - - - DriverConfig driverConfig2 = new DriverConfig(); - driverConfig2.setJdbcDriver("mysql-connector-java-5.1.47.jar"); - driverConfig2.setJdbcDriverClass("com.mysql.jdbc.Driver"); - driverConfig2.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/mysql-connector-java-5.1.47.jar")); - - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig,driverConfig2)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json new file mode 100644 index 000000000..c86cbbcdd --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json @@ -0,0 +1,26 @@ +{ + "dbType": "MYSQL", + "driverConfigList": [ + { + "url": "jdbc:mysql://localhost:3306/", + "defaultDriver": true, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/mysql-connector-java-8.0.30.jar" + ], + "jdbcDriver": "mysql-connector-java-8.0.30.jar", + "jdbcDriverClass": "com.mysql.cj.jdbc.Driver" + }, + { + "url": "jdbc:mysql://localhost:3306/", + "defaultDriver": false, + "custom": false, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/mysql-connector-java-5.1.47.jar" + ], + "jdbcDriver": "mysql-connector-java-5.1.47.jar", + "jdbcDriverClass": "com.mysql.jdbc.Driver" + } + ], + "name": "Mysql" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-oceanbase/pom.xml b/chat2db-server/chat2db-plugins/chat2db-oceanbase/pom.xml index a71c0b581..a528fe2ab 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oceanbase/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-oceanbase/pom.xml @@ -18,5 +18,18 @@ chat2db-oceanbase - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBasePlugin.java b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBasePlugin.java index 28650b12b..3591888d5 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBasePlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBasePlugin.java @@ -1,16 +1,15 @@ package ai.chat2db.plugin.oceanbase; -import ai.chat2db.plugin.oceanbase.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.util.FileUtils; public class OceanBasePlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"oceanbase.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/builder/DBConfigBuilder.java deleted file mode 100644 index dd254c351..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/builder/DBConfigBuilder.java +++ /dev/null @@ -1,20 +0,0 @@ -package ai.chat2db.plugin.oceanbase.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("OceanBase"); - dbConfig.setDbType("OCEANBASE"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("oceanbase-client-2.4.2.jar"); - driverConfig.setJdbcDriverClass("com.oceanbase.jdbc.Driver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/oceanbase-client-2.4.2.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json new file mode 100644 index 000000000..1e96bc9a6 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json @@ -0,0 +1,16 @@ +{ + "dbType": "OCEANBASE", + "driverConfigList": [ + { + "url": "jdbc:oceanbase://localhost:2883/", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/oceanbase-client-2.4.2.jar" + ], + "jdbcDriver": "oceanbase-client-2.4.2.jar", + "jdbcDriverClass": "com.oceanbase.jdbc.Driver" + } + ], + "name": "OceanBase" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/pom.xml b/chat2db-server/chat2db-plugins/chat2db-oracle/pom.xml index a20f6ae1c..7ddfb92b9 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/pom.xml @@ -19,4 +19,18 @@ chat2db-spi + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OraclePlugin.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OraclePlugin.java index 7bc9662ad..08089a471 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OraclePlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OraclePlugin.java @@ -1,16 +1,17 @@ package ai.chat2db.plugin.oracle; -import ai.chat2db.plugin.oracle.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class OraclePlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"oracle.json", DBConfig.class); + } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/DBConfigBuilder.java deleted file mode 100644 index c3f921a93..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/DBConfigBuilder.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.chat2db.plugin.oracle.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("Oracle"); - dbConfig.setDbType("ORACLE"); - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("ojdbc8-19.3.0.0.jar,orai18n-19.3.0.0.jar"); - driverConfig.setJdbcDriverClass("oracle.jdbc.driver.OracleDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/ojdbc8-19.3.0.0.jar", "https://oss.sqlgpt.cn/lib/orai18n-19.3.0.0.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json new file mode 100644 index 000000000..18203f8e1 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json @@ -0,0 +1,17 @@ +{ + "dbType": "ORACLE", + "driverConfigList": [ + { + "url": "jdbc:oracle:thin:@localhost:1521:XE", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/ojdbc8-19.3.0.0.jar", + "https://oss.sqlgpt.cn/lib/orai18n-19.3.0.0.jar" + ], + "jdbcDriver": "ojdbc8-19.3.0.0.jar,orai18n-19.3.0.0.jar", + "jdbcDriverClass": "oracle.jdbc.driver.OracleDriver" + } + ], + "name": "Oracle" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/pom.xml b/chat2db-server/chat2db-plugins/chat2db-postgresql/pom.xml index a3a6c2a6b..1006e9896 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/pom.xml @@ -19,5 +19,18 @@ chat2db-spi - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLPlugin.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLPlugin.java index 7295c2e52..2156c0d2b 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLPlugin.java @@ -1,15 +1,15 @@ package ai.chat2db.plugin.postgresql; -import ai.chat2db.plugin.postgresql.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class PostgreSQLPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"pg.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/DBConfigBuilder.java deleted file mode 100644 index 117822fa0..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/DBConfigBuilder.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.chat2db.plugin.postgresql.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("PostgreSQL"); - dbConfig.setDbType("POSTGRESQL"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("postgresql-42.5.1.jar"); - driverConfig.setJdbcDriverClass("org.postgresql.Driver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/postgresql-42.5.1.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json new file mode 100644 index 000000000..ea52c00e2 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json @@ -0,0 +1,16 @@ +{ + "dbType": "POSTGRESQL", + "driverConfigList": [ + { + "url": "jdbc:postgresql://localhost:5432/postgres", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/postgresql-42.5.1.jar" + ], + "jdbcDriver": "postgresql-42.5.1.jar", + "jdbcDriverClass": "org.postgresql.Driver" + } + ], + "name": "PostgreSQL" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-presto/pom.xml b/chat2db-server/chat2db-plugins/chat2db-presto/pom.xml index 7390865bf..960fe6dfb 100644 --- a/chat2db-server/chat2db-plugins/chat2db-presto/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-presto/pom.xml @@ -17,5 +17,18 @@ chat2db-presto - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoPlugin.java b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoPlugin.java index ca4211211..740e3da1e 100644 --- a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoPlugin.java @@ -1,16 +1,16 @@ package ai.chat2db.plugin.presto; -import ai.chat2db.plugin.presto.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class PrestoPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"presto.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/builder/DBConfigBuilder.java deleted file mode 100644 index 99b53c677..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/builder/DBConfigBuilder.java +++ /dev/null @@ -1,20 +0,0 @@ -package ai.chat2db.plugin.presto.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("Presto"); - dbConfig.setDbType("PRESTO"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("presto-jdbc-0.245.1.jar"); - driverConfig.setJdbcDriverClass("com.facebook.presto.jdbc.PrestoDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/presto-jdbc-0.245.1.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json new file mode 100644 index 000000000..e2b87b54f --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json @@ -0,0 +1,16 @@ +{ + "dbType": "PRESTO", + "driverConfigList": [ + { + "url": "jdbc:presto://localhost:8080/", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/presto-jdbc-0.245.1.jar" + ], + "jdbcDriver": "presto-jdbc-0.245.1.jar", + "jdbcDriverClass": "com.facebook.presto.jdbc.PrestoDriver" + } + ], + "name": "Presto" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml b/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml index 0c0411417..598defa20 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-redis/pom.xml @@ -17,5 +17,18 @@ chat2db-redis - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisPlugin.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisPlugin.java index 4d26009c7..53c988db4 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisPlugin.java @@ -1,17 +1,18 @@ package ai.chat2db.plugin.redis; -import ai.chat2db.plugin.redis.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class RedisPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"redis.json", DBConfig.class); + } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/builder/DBConfigBuilder.java deleted file mode 100644 index a83d5d414..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/builder/DBConfigBuilder.java +++ /dev/null @@ -1,20 +0,0 @@ -package ai.chat2db.plugin.redis.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("Redis"); - dbConfig.setDbType("REDIS"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("redis-jdbc-driver-1.3.jar"); - driverConfig.setJdbcDriverClass("jdbc.RedisDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/redis-jdbc-driver-1.3.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json new file mode 100644 index 000000000..6c0be71d0 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json @@ -0,0 +1,16 @@ +{ + "dbType": "REDIS", + "driverConfigList": [ + { + "url": "jdbc:redis://127.0.0.1:6379/0", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/redis-jdbc-driver-1.3.jar" + ], + "jdbcDriver": "redis-jdbc-driver-1.3.jar", + "jdbcDriverClass": "jdbc.RedisDriver" + } + ], + "name": "Redis" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/pom.xml b/chat2db-server/chat2db-plugins/chat2db-sqlite/pom.xml index ba4717845..84a93aa47 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/pom.xml @@ -16,4 +16,18 @@ chat2db-spi + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqlitePlugin.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqlitePlugin.java index b6ebbfb41..5ebcc4d92 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqlitePlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqlitePlugin.java @@ -1,15 +1,15 @@ package ai.chat2db.plugin.sqlite; -import ai.chat2db.plugin.sqlite.builder.DBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class SqlitePlugin implements Plugin { @Override public DBConfig getDBConfig() { - return DBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"sqlite.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/DBConfigBuilder.java deleted file mode 100644 index 6c7ddeb4c..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/DBConfigBuilder.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.chat2db.plugin.sqlite.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class DBConfigBuilder { - - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("SQLite"); - dbConfig.setDbType("SQLITE"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("sqlite-jdbc-3.39.3.0.jar"); - driverConfig.setJdbcDriverClass("org.sqlite.JDBC"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/sqlite-jdbc-3.39.3.0.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json new file mode 100644 index 000000000..a2acf8c2c --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json @@ -0,0 +1,16 @@ +{ + "dbType": "SQLITE", + "driverConfigList": [ + { + "url": "jdbc:sqlite:identifier.sqlite", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/sqlite-jdbc-3.39.3.0.jar" + ], + "jdbcDriver": "sqlite-jdbc-3.39.3.0.jar", + "jdbcDriverClass": "org.sqlite.JDBC" + } + ], + "name": "SQLite" +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml b/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml index 9d926c4dc..5485f539d 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml @@ -24,5 +24,18 @@ - + + + + src/main/java + + + **/*.json + + + + src/main/resources + + + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerPlugin.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerPlugin.java index d4b16b9a3..2cbbb8295 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerPlugin.java @@ -1,16 +1,16 @@ package ai.chat2db.plugin.sqlserver; -import ai.chat2db.plugin.sqlserver.builder.SqlServerDBConfigBuilder; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.FileUtils; public class SqlServerPlugin implements Plugin { @Override public DBConfig getDBConfig() { - return SqlServerDBConfigBuilder.buildDBConfig(); + return FileUtils.readJsonValue(this.getClass(),"sqlserver.json", DBConfig.class); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerDBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerDBConfigBuilder.java deleted file mode 100644 index 4d8c75e00..000000000 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerDBConfigBuilder.java +++ /dev/null @@ -1,21 +0,0 @@ -package ai.chat2db.plugin.sqlserver.builder; - -import ai.chat2db.spi.config.DBConfig; -import ai.chat2db.spi.config.DriverConfig; -import com.google.common.collect.Lists; - -public class SqlServerDBConfigBuilder { - - public static DBConfig buildDBConfig() { - DBConfig dbConfig = new DBConfig(); - dbConfig.setName("SQLServer"); - dbConfig.setDbType("SQLSERVER"); - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("mssql-jdbc-11.2.1.jre17.jar"); - driverConfig.setJdbcDriverClass("com.microsoft.sqlserver.jdbc.SQLServerDriver"); - driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss.sqlgpt.cn/lib/mssql-jdbc-11.2.1.jre17.jar")); - dbConfig.setDefaultDriverConfig(driverConfig); - dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); - return dbConfig; - } -} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json new file mode 100644 index 000000000..41d41589b --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json @@ -0,0 +1,16 @@ +{ + "dbType": "SQLSERVER", + "driverConfigList": [ + { + "url": "jdbc:sqlserver://localhost:1433;database=master", + "custom": false, + "defaultDriver": true, + "downloadJdbcDriverUrls": [ + "https://oss.sqlgpt.cn/lib/mssql-jdbc-11.2.1.jre17.jar" + ], + "jdbcDriver": "mssql-jdbc-11.2.1.jre17.jar", + "jdbcDriverClass": "com.microsoft.sqlserver.jdbc.SQLServerDriver" + } + ], + "name": "SQLServer" +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DriverConfigConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DriverConfigConverter.java new file mode 100644 index 000000000..97e5e84ae --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DriverConfigConverter.java @@ -0,0 +1,13 @@ +package ai.chat2db.server.domain.core.converter; + +import ai.chat2db.server.domain.repository.entity.JdbcDriverDO; +import ai.chat2db.spi.config.DriverConfig; +import lombok.extern.slf4j.Slf4j; +import org.mapstruct.Mapper; + +@Slf4j +@Mapper(componentModel = "spring") +public abstract class DriverConfigConverter { + public abstract DriverConfig do2Config(JdbcDriverDO driverDO); + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java index 793c7b8ba..8486450fe 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java @@ -89,10 +89,13 @@ public DataResult createWithPermission(DataSourceCreateParam param) { if (dataSourceKind == DataSourceKindEnum.SHARED && !ContextUtils.getLoginUser().getAdmin()) { throw new PermissionDeniedBusinessException(); } + JdbcUtils.removePropertySameAsDefault(param.getDriverConfig()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); dataSourceDO.setGmtCreate(DateUtil.date()); dataSourceDO.setGmtModified(DateUtil.date()); dataSourceDO.setUserId(ContextUtils.getUserId()); + dataSourceDO.setExtendInfo(null); + dataSourceMapper.insert(dataSourceDO); preWarmingData(dataSourceDO.getId()); return DataResult.of(dataSourceDO.getId()); @@ -125,6 +128,7 @@ public DataResult updateWithPermission(DataSourceUpdateParam param) { DataSource dataSource = queryExistent(param.getId(), null).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); + JdbcUtils.removePropertySameAsDefault(param.getDriverConfig()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); dataSourceDO.setGmtModified(DateUtil.date()); dataSourceMapper.updateById(dataSourceDO); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java index 29356a9ee..7cb5f58ae 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java @@ -1,12 +1,7 @@ package ai.chat2db.server.domain.core.impl; -import java.io.File; -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - import ai.chat2db.server.domain.api.service.JdbcDriverService; +import ai.chat2db.server.domain.core.converter.DriverConfigConverter; import ai.chat2db.server.domain.repository.entity.JdbcDriverDO; import ai.chat2db.server.domain.repository.mapper.JdbcDriverMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -22,6 +17,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static ai.chat2db.spi.util.JdbcUtils.setDriverDefaultProperty; + + @Slf4j @Service public class JdbcDriverServiceImpl implements JdbcDriverService { @@ -29,50 +34,31 @@ public class JdbcDriverServiceImpl implements JdbcDriverService { @Autowired private JdbcDriverMapper jdbcDriverMapper; + @Autowired + private DriverConfigConverter driverConfigConverter; + @Override public DataResult getDrivers(String dbType) { Map driverConfigMap = new LinkedHashMap<>(); LambdaQueryWrapper query = new LambdaQueryWrapper(); query.eq(JdbcDriverDO::getDbType, dbType); List driverDOS = jdbcDriverMapper.selectList(query); + List driverConfigs = Lists.newArrayList(); if (!CollectionUtils.isEmpty(driverDOS)) { - for (JdbcDriverDO driverConfig : driverDOS) { - String[] jarPaths = driverConfig.getJdbcDriver().split(","); - boolean flag = true; - for (String jarPath : jarPaths) { - File file = new File(JdbcJarUtils.PATH + jarPath); - if (!file.exists()) { - flag = false; - break; - } - } - if (flag && driverConfigMap.get(driverConfig.getJdbcDriver()) == null) { - DriverConfig dc = new DriverConfig(); - dc.setCustom(true); - dc.setDbType(driverConfig.getDbType()); - dc.setJdbcDriver(driverConfig.getJdbcDriver()); - dc.setJdbcDriverClass(driverConfig.getJdbcDriverClass()); - driverConfigMap.put(driverConfig.getJdbcDriver(), dc); - } else { - log.warn("Driver file not found: {}", driverConfig.getJdbcDriver()); - } - } + driverConfigs = driverDOS.stream().map(driverConfigConverter::do2Config).collect(Collectors.toList()); } DBConfig dbConfig = Chat2DBContext.PLUGIN_MAP.get(dbType).getDBConfig(); List driverConfigList = dbConfig.getDriverConfigList(); - for (DriverConfig driverConfig : driverConfigList) { - String[] jarPaths = driverConfig.getJdbcDriver().split(","); - boolean flag = true; - for (String jarPath : jarPaths) { - File file = new File(JdbcJarUtils.PATH + jarPath); - if (!file.exists()) { - flag = false; - break; - } - } + if (CollectionUtils.isNotEmpty(driverConfigList)) { + driverConfigs.addAll(driverConfigList); + } + + for (DriverConfig driverConfig : driverConfigs) { + boolean flag = driverExists(driverConfig); if (flag && driverConfigMap.get(driverConfig.getJdbcDriver()) == null) { driverConfigMap.put(driverConfig.getJdbcDriver(), driverConfig); + setDriverDefaultProperty(driverConfig); } else { log.warn("Driver file not found: {}", driverConfig.getJdbcDriver()); } @@ -81,6 +67,20 @@ public DataResult getDrivers(String dbType) { return DataResult.of(dbConfig); } + + private boolean driverExists(DriverConfig driverConfig) { + boolean flag = true; + String[] jarPaths = driverConfig.getJdbcDriver().split(","); + for (String jarPath : jarPaths) { + File file = new File(JdbcJarUtils.PATH + jarPath); + if (!file.exists()) { + flag = false; + break; + } + } + return flag; + } + @Override public ActionResult upload(String dbType, String jdbcDriverClass, String localPath) { JdbcDriverDO driverDO = new JdbcDriverDO(); diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java new file mode 100644 index 000000000..451a80a51 --- /dev/null +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.test.temp; + +import java.sql.Driver; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; + +public class DriverTest { + + public static void main(String[] args) throws SQLException { + + Driver driver = new org.mariadb.jdbc.Driver(); + DriverPropertyInfo[] driverPropertyInfos = driver.getPropertyInfo("jdbc:mariadb://localhost:3306", null); + System.out.println(driverPropertyInfos.length); + } +} diff --git a/chat2db-server/chat2db-spi/pom.xml b/chat2db-server/chat2db-spi/pom.xml index c9fe9afa7..5512e724b 100644 --- a/chat2db-server/chat2db-spi/pom.xml +++ b/chat2db-server/chat2db-spi/pom.xml @@ -77,7 +77,7 @@ org.locationtech.jts jts-core - 1.18.1 + 1.19.0 \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java index aa997c9d8..f2a2c4f3b 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java @@ -1,7 +1,7 @@ package ai.chat2db.spi.config; -import lombok.Data; +import org.apache.commons.collections4.CollectionUtils; import java.util.List; @@ -9,7 +9,6 @@ * @author jipengfei * @version : DBConfig.java */ -@Data public class DBConfig { /** @@ -32,7 +31,6 @@ public class DBConfig { */ private List driverConfigList; - /** * 建表语句 */ @@ -42,4 +40,73 @@ public class DBConfig { * 修改表结构 */ private String simpleAlterTable; + + + public String getDbType() { + return dbType; + } + + public void setDbType(String dbType) { + this.dbType = dbType; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public DriverConfig getDefaultDriverConfig() { + if (this.defaultDriverConfig != null) { + return this.defaultDriverConfig; + } else { + if (!CollectionUtils.isEmpty(driverConfigList)) { + for (DriverConfig driverConfig : driverConfigList) { + if (driverConfig.isDefaultDriver()) { + return driverConfig; + } + } + return driverConfigList.get(0); + } + } + return null; + } + + public void setDefaultDriverConfig(DriverConfig defaultDriverConfig) { + this.defaultDriverConfig = defaultDriverConfig; + } + + public List getDriverConfigList() { + return driverConfigList; + } + + public void setDriverConfigList(List driverConfigList) { + this.driverConfigList = driverConfigList; + if (!CollectionUtils.isEmpty(driverConfigList)) { + for (DriverConfig driverConfig : driverConfigList) { + if (driverConfig.isDefaultDriver()) { + this.defaultDriverConfig = driverConfig; + break; + } + } + } + } + + public String getSimpleCreateTable() { + return simpleCreateTable; + } + + public void setSimpleCreateTable(String simpleCreateTable) { + this.simpleCreateTable = simpleCreateTable; + } + + public String getSimpleAlterTable() { + return simpleAlterTable; + } + + public void setSimpleAlterTable(String simpleAlterTable) { + this.simpleAlterTable = simpleAlterTable; + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DriverConfig.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DriverConfig.java index 03e6bdda4..3dc2c4802 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DriverConfig.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DriverConfig.java @@ -3,6 +3,7 @@ import java.util.List; +import ai.chat2db.spi.model.KeyValue; import lombok.Data; import org.apache.commons.lang3.StringUtils; @@ -12,6 +13,11 @@ */ @Data public class DriverConfig { + + /** + * url + */ + private String url; /** * jdbcDriver */ @@ -22,17 +28,14 @@ public class DriverConfig { */ private String jdbcDriverClass; - ///** - // * name - // */ - //private String name; - /** * downloadJdbcDriverUrls */ private List downloadJdbcDriverUrls; - + /** + * dbType + */ private String dbType; /** @@ -40,6 +43,13 @@ public class DriverConfig { */ private boolean custom; + /** + * properties + */ + private List extendInfo; + + + private boolean defaultDriver; public boolean notEmpty() { return StringUtils.isNotBlank(getJdbcDriver()) && StringUtils.isNotBlank( diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/KeyValue.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/KeyValue.java index 1716356e6..3edacebdc 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/KeyValue.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/KeyValue.java @@ -23,7 +23,17 @@ public class KeyValue implements Serializable { /** * 属性值 */ - private Object value; + private String value; + + /** + * 是否必填 + */ + private boolean required; + + /** + * 选项 + */ + private List choices; public static Map toMap(List keyValues) { if (CollectionUtils.isEmpty(keyValues)) { diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java index d841646a0..333f388a4 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java @@ -7,6 +7,7 @@ import java.net.URLClassLoader; import java.sql.Connection; import java.sql.Driver; +import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.util.Map; import java.util.Properties; @@ -39,7 +40,7 @@ public static Connection getConnection(String url, DriverConfig driver) throws S } public static Connection getConnection(String url, String user, String password, DriverConfig driver) - throws SQLException { + throws SQLException { Properties info = new Properties(); if (user != null) { info.put("user", user); @@ -53,8 +54,8 @@ public static Connection getConnection(String url, String user, String password, } public static Connection getConnection(String url, String user, String password, DriverConfig driver, - Map properties) - throws SQLException { + Map properties) + throws SQLException { Properties info = new Properties(); if (StringUtils.isNotEmpty(user)) { info.put("user", user); @@ -74,7 +75,7 @@ public static Connection getConnection(String url, String user, String password, } public static Connection getConnection(String url, Properties info, DriverConfig driver) - throws SQLException { + throws SQLException { if (url == null) { throw new SQLException("The url cannot be null", "08001"); } @@ -84,8 +85,8 @@ public static Connection getConnection(String url, Properties info, DriverConfig } try { Connection connection = driverEntry.getDriver().connect(url, info); - if(connection == null){ - throw new SQLException("driver.connect return null , No suitable driver found for url " +url ,"08001"); + if (connection == null) { + throw new SQLException("driver.connect return null , No suitable driver found for url " + url, "08001"); } return connection; } catch (SQLException var7) { @@ -94,13 +95,31 @@ public static Connection getConnection(String url, Properties info, DriverConfig return con; } else { throw new SQLException("Cannot create connection (" + var7.getMessage() + ")", "08001", - var7); + var7); } } } + public static DriverPropertyInfo[] getProperty(DriverConfig driver) + throws SQLException { + if (driver == null) { + return null; + } + DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); + if (driverEntry == null) { + driverEntry = getJDBCDriver(driver); + } + try { + String url = driver.getUrl() == null ? "" : driver.getUrl(); + return driverEntry.getDriver().getPropertyInfo(url, null); + } catch (Exception var7) { + return null; + } + } + + private static Connection tryConnectionAgain(DriverEntry driverEntry, String url, - Properties info) throws SQLException { + Properties info) throws SQLException { if (url.contains("mysql")) { if (!info.containsKey("useSSL")) { info.put("useSSL", "false"); @@ -111,14 +130,14 @@ private static Connection tryConnectionAgain(DriverEntry driverEntry, String url } private static DriverEntry getJDBCDriver(DriverConfig driver) - throws SQLException { + throws SQLException { synchronized (driver) { try { if (DRIVER_ENTRY_MAP.containsKey(driver.getJdbcDriver())) { return DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); } ClassLoader cl = getClassLoader(driver); - Driver d = (Driver)cl.loadClass(driver.getJdbcDriverClass()).newInstance(); + Driver d = (Driver) cl.loadClass(driver.getJdbcDriverClass()).newInstance(); DriverEntry driverEntry = DriverEntry.builder().driverConfig(driver).driver(d).build(); DRIVER_ENTRY_MAP.put(driver.getJdbcDriver(), driverEntry); return driverEntry; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java new file mode 100644 index 000000000..496640d06 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java @@ -0,0 +1,21 @@ +package ai.chat2db.spi.util; + +import ai.chat2db.spi.config.DBConfig; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +public class FileUtils { + + public static T readJsonValue(Class loaderClass, String path, Class clazz) { + ObjectMapper mapper = new ObjectMapper(); + T value = null; + try { + value = mapper.readValue(loaderClass.getResourceAsStream(path), clazz); + // 使用obj中的数据 + } catch (IOException e) { + return null; + } + return value; + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcUtils.java index bf53b8a97..36b7f8405 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcUtils.java @@ -1,11 +1,10 @@ package ai.chat2db.spi.util; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.Map; +import java.sql.*; +import java.text.Collator; +import java.util.*; +import ai.chat2db.spi.model.KeyValue; import com.alibaba.druid.DbType; import ai.chat2db.spi.config.DriverConfig; @@ -14,8 +13,11 @@ import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.ssh.SSHManager; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.lang.Nullable; @@ -138,11 +140,11 @@ private static int getTypeByTypeName(String typeName, int type) { * @return */ public static DataSourceConnect testConnect(String url, String host, String port, - String userName, String password, String dbType, - DriverConfig driverConfig, SSHInfo ssh, Map properties) { + String userName, String password, String dbType, + DriverConfig driverConfig, SSHInfo ssh, Map properties) { DataSourceConnect dataSourceConnect = DataSourceConnect.builder() - .success(Boolean.TRUE) - .build(); + .success(Boolean.TRUE) + .build(); Session session = null; Connection connection = null; // 加载驱动 @@ -155,7 +157,7 @@ public static DataSourceConnect testConnect(String url, String host, String port } // 创建连接 connection = IDriverManager.getConnection(url, userName, password, - driverConfig, properties); + driverConfig, properties); } catch (Exception e) { log.error("connection fail:", e); dataSourceConnect.setSuccess(Boolean.FALSE); @@ -203,4 +205,96 @@ public static void closeResultSet(@Nullable ResultSet rs) { } + public static void setDriverDefaultProperty(DriverConfig driverConfig) { + if(driverConfig == null){ + return; + } + List defaultKeyValues = driverConfig.getExtendInfo(); + Map valueMap = Maps.newHashMap(); + if (!CollectionUtils.isEmpty(defaultKeyValues)) { + for (KeyValue keyValue : defaultKeyValues) { + if (keyValue == null || StringUtils.isBlank(keyValue.getKey())) { + continue; + } + valueMap.put(keyValue.getKey(), keyValue); + } + } + try { + DriverPropertyInfo[] propertyInfos = IDriverManager.getProperty(driverConfig); + if (propertyInfos == null) { + return; + } + for (int i = 0; i < propertyInfos.length; i++) { + DriverPropertyInfo propertyInfo = propertyInfos[i]; + if (propertyInfo == null) { + continue; + } + KeyValue keyValue = valueMap.get(propertyInfo.name); + if (keyValue != null) { + String[] choices = propertyInfo.choices; + if (CollectionUtils.isEmpty(keyValue.getChoices()) && choices != null && choices.length > 0) { + keyValue.setChoices(Lists.newArrayList(choices)); + } + } else { + keyValue = new KeyValue(); + keyValue.setKey(propertyInfo.name); + keyValue.setValue(propertyInfo.value); + keyValue.setRequired(propertyInfo.required); + String[] choices = propertyInfo.choices; + if (choices != null && choices.length > 0) { + keyValue.setChoices(Lists.newArrayList(choices)); + } + valueMap.put(keyValue.getKey(), keyValue); + } + } + if (!valueMap.isEmpty()) { + Comparator comparator = Collator.getInstance(Locale.ENGLISH); + List result = new ArrayList<>(valueMap.values()); + Collections.sort(result, (o1, o2) -> comparator.compare(o1.getKey(), o2.getKey())); + driverConfig.setExtendInfo(result); + } + } catch (SQLException e) { + log.error("get property error:", e); + } + } + + public static void removePropertySameAsDefault(DriverConfig driverConfig) { + if(driverConfig == null){ + return; + } + List customValue = driverConfig.getExtendInfo(); + if (CollectionUtils.isEmpty(customValue)) { + return ; + } + Map map = Maps.newHashMap(); + List result = new ArrayList<>(); + try { + DriverPropertyInfo[] propertyInfos = IDriverManager.getProperty(driverConfig); + if (propertyInfos == null) { + return ; + } + for (int i = 0; i < propertyInfos.length; i++) { + DriverPropertyInfo propertyInfo = propertyInfos[i]; + if (propertyInfo == null) { + continue; + } + map.put(propertyInfo.name, propertyInfo.value); + } + for (KeyValue keyValue : customValue) { + if (keyValue == null || StringUtils.isBlank(keyValue.getKey())) { + continue; + } + String value = map.get(keyValue.getKey()); + if (!StringUtils.equals(value, keyValue.getValue())) { + result.add(keyValue); + } + } + Comparator comparator = Collator.getInstance(Locale.ENGLISH); + Collections.sort(result, (o1, o2) -> comparator.compare(o1.getKey(), o2.getKey())); + driverConfig.setExtendInfo(result); + } catch (SQLException e) { + + } + } + } From 8248a5f83810b8a109206cf1afc98bda6e84de1b Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 12 Nov 2023 22:38:34 +0800 Subject: [PATCH 039/126] Driver configuration is configured through json, Support driver attribute configuration. --- .../java/ai/chat2db/plugin/mysql/mysql.json | 18 ++++++++++++-- .../chat2db/plugin/sqlserver/sqlserver.json | 24 ++++++++++++++++++- .../chat2db/server/test/temp/DriverTest.java | 15 ------------ 3 files changed, 39 insertions(+), 18 deletions(-) delete mode 100644 chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json index c86cbbcdd..d326f1b28 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json @@ -9,7 +9,14 @@ "https://oss.sqlgpt.cn/lib/mysql-connector-java-8.0.30.jar" ], "jdbcDriver": "mysql-connector-java-8.0.30.jar", - "jdbcDriverClass": "com.mysql.cj.jdbc.Driver" + "jdbcDriverClass": "com.mysql.cj.jdbc.Driver", + "extendInfo": [ + { + "key": "zeroDateTimeBehavior", + "value": "convertToNull", + "required": false + } + ] }, { "url": "jdbc:mysql://localhost:3306/", @@ -19,7 +26,14 @@ "https://oss.sqlgpt.cn/lib/mysql-connector-java-5.1.47.jar" ], "jdbcDriver": "mysql-connector-java-5.1.47.jar", - "jdbcDriverClass": "com.mysql.jdbc.Driver" + "jdbcDriverClass": "com.mysql.jdbc.Driver", + "extendInfo": [ + { + "key": "characterEncoding", + "value": "UTF-8", + "required": false + } + ] } ], "name": "Mysql" diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json index 41d41589b..ee8faea3c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json @@ -9,7 +9,29 @@ "https://oss.sqlgpt.cn/lib/mssql-jdbc-11.2.1.jre17.jar" ], "jdbcDriver": "mssql-jdbc-11.2.1.jre17.jar", - "jdbcDriverClass": "com.microsoft.sqlserver.jdbc.SQLServerDriver" + "jdbcDriverClass": "com.microsoft.sqlserver.jdbc.SQLServerDriver", + "extendInfo": [ + { + "key": "encrypt", + "value": "false", + "required": false + }, + { + "key": "trustServerCertificate", + "value": "true", + "required": false + }, + { + "key": "integratedSecurity", + "value": "false", + "required": false + }, + { + "key": "Trusted_Connection", + "value": "yes", + "required": false + } + ] } ], "name": "SQLServer" diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java deleted file mode 100644 index 451a80a51..000000000 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/DriverTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package ai.chat2db.server.test.temp; - -import java.sql.Driver; -import java.sql.DriverPropertyInfo; -import java.sql.SQLException; - -public class DriverTest { - - public static void main(String[] args) throws SQLException { - - Driver driver = new org.mariadb.jdbc.Driver(); - DriverPropertyInfo[] driverPropertyInfos = driver.getPropertyInfo("jdbc:mariadb://localhost:3306", null); - System.out.println(driverPropertyInfos.length); - } -} From 2e31890381ab29d2c374bafa6a7915a4175e7635 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 12 Nov 2023 22:50:40 +0800 Subject: [PATCH 040/126] Driver configuration is configured through json, Support driver attribute configuration. --- .../src/main/java/ai/chat2db/spi/sql/IDriverManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java index b57674643..1d1862d29 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java @@ -106,7 +106,7 @@ public static Connection getConnection(String url, Properties info, DriverConfig public static DriverPropertyInfo[] getProperty(DriverConfig driver) throws SQLException { - if (driver == null) { + if (Objects.isNull(driver)) { return null; } DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); @@ -114,7 +114,7 @@ public static DriverPropertyInfo[] getProperty(DriverConfig driver) driverEntry = getJDBCDriver(driver); } try { - String url = driver.getUrl() == null ? "" : driver.getUrl(); + String url = Objects.isNull(driver.getUrl()) ? "" : driver.getUrl(); return driverEntry.getDriver().getPropertyInfo(url, null); } catch (Exception var7) { return null; From e0c9b4e165afc05afde8a87926671d6c0f927f2e Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 13 Nov 2023 08:49:05 +0800 Subject: [PATCH 041/126] chore: Add script to build local app --- chat2db-client/script/local-client-build.sh | 0 chat2db-client/src/main/menu.js | 6 ++-- script/local-client-build.sh | 34 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) delete mode 100644 chat2db-client/script/local-client-build.sh create mode 100644 script/local-client-build.sh diff --git a/chat2db-client/script/local-client-build.sh b/chat2db-client/script/local-client-build.sh deleted file mode 100644 index e69de29bb..000000000 diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index a0d4eb9ee..311d68463 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -110,21 +110,21 @@ const registerAppMenu = (mainWindow, orgs) => { { label: '访问官网', click() { - const url = 'https://chat2db.ai/'; + const url = 'https://www.sqlgpt.cn/zh'; shell.openExternal(url); }, }, { label: '查看文档', click() { - const url = 'https://doc.chat2db.ai/'; + const url = 'https://doc.sqlgpt.cn/zh/'; shell.openExternal(url); }, }, { label: '查看更新日志', click() { - const url = 'https://doc.chat2db.ai/changelog/'; + const url = 'https://doc.sqlgpt.cn/zh/changelog/'; shell.openExternal(url); }, }, diff --git a/script/local-client-build.sh b/script/local-client-build.sh new file mode 100644 index 000000000..536d04784 --- /dev/null +++ b/script/local-client-build.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# JRE_DIR="${JAVA_HOME}/jre" +JRE_DIR="~/Library/Java/JavaVirtualMachines/corretto-17.0.8.1/Contents/Home/" +JRE_TARGET_DIR="chat2db-client/static/jre" +CURRENT_ID="123" + +# Clean +echo "Clean" +rm -rf chat2db-client/static +rm -rf chat2db-client/versions +rm -rf chat2db-client/release + +# 使用 mkdir 创建目录,并使用 -p 参数确保如果目录已存在不会报错 +mkdir -p "$JRE_TARGET_DIR" + +# 使用 cp 命令复制 JAVA_HOME 目录内容到目标目录 +# -r 参数表示递归复制整个目录 +cp -r "${JRE_DIR}/" "$JRE_TARGET_DIR" +chmod -R 777 "$JRE_TARGET_DIR" + +# 打包后端代码 +mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml +mkdir -p chat2db-client/versions/99.0.${CURRENT_ID}/static +echo -n 99.0.${CURRENT_ID} > chat2db-client/versions/version +cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${CURRENT_ID}/static/ + +# 打包前端代码 +cd chat2db-client +yarn install +yarn run build:web:desktop --app_port=10822 +cp -r dist ./versions/99.0.${CURRENT_ID}/ +# 打包客户端 +yarn run build:main:prod -c.productName=Chat2DB-Test -c.extraMetadata.version=99.0.${CURRENT_ID} --mac --arm64 From e85abc55a7411f096de876136da5c11ec81b76d0 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 13 Nov 2023 10:37:52 +0800 Subject: [PATCH 042/126] chore: file adjustment --- chat2db-client/.umirc.ts | 15 ++------------- chat2db-client/src/components/Output/index.less | 1 + chat2db-client/src/layouts/index.tsx | 11 ----------- chat2db-client/src/pages/main/chat/index.less | 8 -------- chat2db-client/src/pages/main/chat/index.tsx | 14 -------------- 5 files changed, 3 insertions(+), 46 deletions(-) delete mode 100644 chat2db-client/src/pages/main/chat/index.less delete mode 100644 chat2db-client/src/pages/main/chat/index.tsx diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index fe3b19cee..e0c7876a8 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -20,12 +20,7 @@ export default defineConfig({ publicPath: '/', hash: true, routes: [ - { path: '/demo', component: '@/pages/demo' }, - { path: '/connections', component: 'main' }, - { path: '/workspace', component: 'main' }, - { path: '/dashboard', component: 'main' }, { path: '/login', component: '@/pages/login' }, - { path: '/test', component: '@/pages/test' }, { path: '/', component: 'main' }, ], @@ -50,13 +45,7 @@ export default defineConfig({ // rel: 'manifest', // href: 'manifest.json', // }], - links: [ - { rel:"icon", - type:"image/ico", - sizes:"32x32", - href:"/static/front/logo.ico" - } - ], + links: [{ rel: 'icon', type: 'image/ico', sizes: '32x32', href: '/static/front/logo.ico' }], headScripts: [ `if (localStorage.getItem('app-local-storage-versions') !== 'v3') { localStorage.clear(); @@ -100,5 +89,5 @@ export default defineConfig({ __APP_VERSION__: yarn_config.app_version || '0.0.0', __APP_PORT__: yarn_config.app_port, }, - esbuildMinifyIIFE: true + esbuildMinifyIIFE: true, }); diff --git a/chat2db-client/src/components/Output/index.less b/chat2db-client/src/components/Output/index.less index d21fa3ea1..1a31e1c7b 100644 --- a/chat2db-client/src/components/Output/index.less +++ b/chat2db-client/src/components/Output/index.less @@ -33,6 +33,7 @@ } .timeSpan{ margin-right: 4px; + font-weight: 500; } .iconBox { transform: rotate(90deg); diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index d088530ef..2b8e1b2c2 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -184,17 +184,6 @@ function AppContainer() { {startSchedule < 2 && (
- {/* 状态等于1时,说明没服务起来需要轮训接口,这时可能服务配置又问题,需要设置来修改 */} - {/* {startSchedule === 1 && ( - - -
- } - noLogin - /> - )} */} {serviceFail && ( <>
diff --git a/chat2db-client/src/pages/main/chat/index.less b/chat2db-client/src/pages/main/chat/index.less deleted file mode 100644 index 79b7401d7..000000000 --- a/chat2db-client/src/pages/main/chat/index.less +++ /dev/null @@ -1,8 +0,0 @@ -.box { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: var(--color-bg); -} diff --git a/chat2db-client/src/pages/main/chat/index.tsx b/chat2db-client/src/pages/main/chat/index.tsx deleted file mode 100644 index 13e7dde9d..000000000 --- a/chat2db-client/src/pages/main/chat/index.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { memo } from 'react'; -import styles from './index.less'; -import classnames from 'classnames'; - -interface IProps { - className?: string; -} - -export default memo(function Chat(props) { - const { className } = props - return
- Chat -
-}) From 524fa8f823a76a0757a8d52742b367da0382a12d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 13 Nov 2023 11:07:45 +0800 Subject: [PATCH 043/126] tabs --- chat2db-client/src/hooks/useGetConsoleList.ts | 21 +++++ .../components/NewWorkspaceRight/index.less | 17 ++++ .../components/NewWorkspaceRight/index.tsx | 19 +++++ .../components/WorkspaceLeft/index.tsx | 1 + .../components/WorkspaceRight/index.less | 7 +- .../components/WorkspaceTabs/index.tsx | 81 +++++++++++++++++++ .../src/pages/main/workspace/index.tsx | 6 +- chat2db-client/src/service/history.ts | 6 +- chat2db-client/src/store/console/index.ts | 55 ++++++------- chat2db-client/src/typings/console.ts | 17 ++++ 10 files changed, 186 insertions(+), 44 deletions(-) create mode 100644 chat2db-client/src/hooks/useGetConsoleList.ts create mode 100644 chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.tsx create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx create mode 100644 chat2db-client/src/typings/console.ts diff --git a/chat2db-client/src/hooks/useGetConsoleList.ts b/chat2db-client/src/hooks/useGetConsoleList.ts new file mode 100644 index 000000000..5d23918fe --- /dev/null +++ b/chat2db-client/src/hooks/useGetConsoleList.ts @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; + +import { useConsoleStore, getSavedConsoleList } from '@/store/console'; + +const useGetConsoleList = () => { + const { consoleList } = useConsoleStore((state) => { + return { + consoleList: state.consoleList, + }; + }); + + useEffect(() => { + getSavedConsoleList(); + }, []); + + return { + consoleList, + } +}; + +export default useGetConsoleList; diff --git a/chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.less b/chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.less new file mode 100644 index 000000000..1c4b3752d --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.less @@ -0,0 +1,17 @@ +@import '../../../../../styles/var.less'; + +.workspaceRight { + flex: 1; + width: 0px; + display: flex; + flex-direction: column; +} + +.consoleTabsContainer{ + height: 50%; +} + +.searchResultContainer{ + border-top: 1px solid var(--color-border-secondary); + flex: 1; +} diff --git a/chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.tsx b/chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.tsx new file mode 100644 index 000000000..12b7440c5 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/NewWorkspaceRight/index.tsx @@ -0,0 +1,19 @@ +import React, { memo, useEffect, useState, useMemo, Fragment, useRef } from 'react'; +import i18n from '@/i18n'; +import styles from './index.less'; +import classnames from 'classnames'; + +// ----- components ----- +import WorkspaceTabs from '../WorkspaceTabs'; + + +const WorkspaceRight = memo(() => { + + return ( +
+ +
+ ); +}); + +export default WorkspaceRight; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx index f5df192a9..baa8daa6b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -9,6 +9,7 @@ const WorkspaceLeft = memo(() => { return (
+
这里加分割线还有操作按键?
); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less index fe2326b78..c66d1412d 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -1,11 +1,7 @@ @import '../../../../../styles/var.less'; .workspaceRight { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + flex: 1; :global { .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { color: var(--color-text) !important; @@ -16,6 +12,7 @@ .workspaceRightMain { display: flex; + height: 100%; } .workspaceExtend { diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx new file mode 100644 index 000000000..c8ff525dd --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx @@ -0,0 +1,81 @@ +import React, { memo, useEffect, useState, useMemo, Fragment, useRef } from 'react'; +import i18n from '@/i18n'; +import classnames from 'classnames'; + +// ----- constants ----- +import { WorkspaceTabType, workspaceTabConfig } from '@/constants'; +// import WorkspaceExtend from '../WorkspaceExtend'; + +// ----- components ----- +import Tabs, { ITabItem } from '@/components/Tabs'; + +// ----- hooks ----- +import useGetConsoleList from '@/hooks/useGetConsoleList'; + +// ---- store ----- +import { useConsoleStore } from '@/store/console'; +import { State } from '@/components/StateIndicator'; + +const WorkspaceTabs = memo(() => { + // 获取console + const { consoleList } = useGetConsoleList(); + + const { activeConsoleId } = useConsoleStore(state=> { + return { + activeConsoleId: state.activeConsoleId + } + }); + + const workspaceTabItems: ITabItem[] = useMemo(() => { + return ( + consoleList?.map((item) => { + return { + prefixIcon: workspaceTabConfig[item.type]?.icon, + label: item.name, + key: item.id, + // editableName: item.type === WorkspaceTabType.CONSOLE, + }; + }) || [] + ); + }, [consoleList]); + + // 删除 新增tab + const onEdit = (action: 'add' | 'remove', data: ITabItem[]) => { + if (action === 'remove') { + setWorkspaceTabList( + workspaceTabList.filter((t) => { + return data.findIndex((item) => item.key === t.id) === -1; + }), + ); + data.forEach((item) => { + const editData = workspaceTabList?.find((t) => t.id === item.key); + if ( + editData?.type !== WorkspaceTabType.EditTable && + editData?.type !== WorkspaceTabType.CreateTable && + editData?.type !== WorkspaceTabType.EditTableData + ) { + closeWindowTab(item.key as number); + } + }) + } + if (action === 'add') { + addConsole(); + } + }; + + function onTabChange(key: string | number | null) { + setActiveConsoleId(key); + } + + return ( + + ); +}); + +export default WorkspaceTabs; diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index 9fdc33d92..08f43d8ad 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -5,7 +5,7 @@ import { useWorkspaceStore } from '@/store/workspace'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; -// import WorkspaceRight from './components/WorkspaceRight'; +import NewWorkspaceRight from './components/NewWorkspaceRight'; import useMonacoTheme from '@/components/Console/MonacoEditor/useMonacoTheme'; @@ -33,9 +33,7 @@ const workspacePage = memo(() => { >
-
- {/* */} -
+
); diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index cbb76576f..d8094baed 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -1,12 +1,10 @@ import createRequest from "./base"; // import { IPageResponse,IPageParams,IHistoryRecord, IWindowTab, ISavedConsole } from '@/types'; -import { ConsoleOpenedStatus, DatabaseTypeCode, ConsoleStatus } from '@/constants' +import { DatabaseTypeCode, ConsoleStatus } from '@/constants' import { ICreateConsole, IConsole, IPageResponse, IPageParams } from '@/typings'; export interface IGetSavedListParams extends IPageParams { - dataSourceId?: number; - databaseName?: string; - tabOpened?: ConsoleOpenedStatus; + tabOpened?: 'y' | 'n'; status?: ConsoleStatus } export interface IGetHistoryListParams extends IPageParams { diff --git a/chat2db-client/src/store/console/index.ts b/chat2db-client/src/store/console/index.ts index 2f460bd9a..a94217a43 100644 --- a/chat2db-client/src/store/console/index.ts +++ b/chat2db-client/src/store/console/index.ts @@ -4,40 +4,33 @@ import { create, UseBoundStore, StoreApi } from 'zustand'; import { devtools } from 'zustand/middleware'; -import { IConnectionListItem, IConnectionEnv } from '@/typings/connection'; -import connectionService from '@/service/connection'; +import { IConsole } from '@/typings/console'; +import historyService from '@/service/history'; -export interface IConnectionStore { - consoleList: IConnectionListItem[] | null; - connectionEnvList: IConnectionEnv[] | null; - setConnectionList: (connectionList: IConnectionListItem[]) => void; - setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => void; - getConnectionList: () => Promise; +export interface IConsoleStore { + consoleList: IConsole[] | null; + activeConsoleId: string | null; } -export const connectionStore = (set): IConnectionStore => ({ +const initConsoleStore = { consoleList: null, - connectionEnvList: null, - setConnectionList: (connectionList: IConnectionListItem[]) => set({ connectionList }), - setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => set({ connectionEnvList }), - getConnectionList: () => { - return connectionService - .getList({ - pageNo: 1, - pageSize: 1000, - refresh: true, - }) - .then((res) => { - set({ connectionList: res?.data || [] }); - }) - .catch(() => { - set({ connectionList: [] }); - }); - }, -}); + activeConsoleId: null +} -export const useConnectionStore: UseBoundStore> = create( - devtools((set) => ({ - ...connectionStore(set), - })), +export const useConsoleStore: UseBoundStore> = create( + devtools(() => (initConsoleStore)), ); + +export const getSavedConsoleList = () => { + historyService.getSavedConsoleList({ + tabOpened: 'y', + pageNo: 1, + pageSize: 20, + }).then((res) => { + useConsoleStore.setState({ consoleList: res?.data }); + }); +} + +export const setActiveConsoleId = (id: string) => { + useConsoleStore.setState({ activeConsoleId: id }); +} diff --git a/chat2db-client/src/typings/console.ts b/chat2db-client/src/typings/console.ts new file mode 100644 index 000000000..7ababd59f --- /dev/null +++ b/chat2db-client/src/typings/console.ts @@ -0,0 +1,17 @@ +import { ConsoleStatus, DatabaseTypeCode, WorkspaceTabType } from '@/constants'; + +// 控制台详情 +export interface IConsole { + id: number; // consoleId + name: string; // 控制台名称 + ddl: string; // 控制台内的sql + dataSourceId: number; // 数据源id + dataSourceName: string; // 数据源名称 + databaseName?: string; // 数据库名称 + schemaName?: string; // schema名称 + type: DatabaseTypeCode; // 数据库类型 + status: ConsoleStatus; // 控制台状态 + connectable: boolean; // 是否可连接 + tabOpened?: 'y' | 'n'; // 控制台tab是否打开 + operationType: WorkspaceTabType; // 操作类型 +} From 6a28e4fd15e440db54a7388c80456afa805ebcaa Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 13 Nov 2023 13:13:12 +0800 Subject: [PATCH 044/126] fix:Copy as insert first row lost problem --- CHANGELOG.md | 9 +++++++++ CHANGELOG_CN.md | 9 +++++++++ .../SearchResult/components/TableBox/index.tsx | 2 -- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f64d6d1c..63e1e6b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 3.0.12 + +`2023-11-13` + +**更新日志** + +- 🐞【Fixed】Copy as insert first row lost problem + + ## 3.0.11 `2023-11-08` diff --git a/CHANGELOG_CN.md b/CHANGELOG_CN.md index e0df3f2e1..4525f85d9 100644 --- a/CHANGELOG_CN.md +++ b/CHANGELOG_CN.md @@ -1,3 +1,12 @@ +## 3.0.12 + +`2023-11-13` + +**更新日志** + +- 🐞【修复】复制为insert第一行丢失问题 + + ## 3.0.11 `2023-11-10` diff --git a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx index b772104d0..cb46c46a1 100644 --- a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx @@ -768,7 +768,6 @@ export default function TableBox(props: ITableProps) { const newRowDatas = tableData.filter((item) => rowIds.includes(item[colNoCode]!)); const newRowDatasList = newRowDatas.map((item) => { const _item = lodash.cloneDeep(item); - delete _item[colNoCode]; return Object.keys(_item).map((i) => _item[i]); }); const _updateDatas = newRowDatasList.map((item, index) => { @@ -791,7 +790,6 @@ export default function TableBox(props: ITableProps) { const newRowDatas = tableData.filter((item) => rowIds.includes(item[colNoCode]!)); const newRowDatasList = newRowDatas.map((item) => { const _item = lodash.cloneDeep(item); - delete _item[colNoCode]; return Object.keys(_item).map((i) => _item[i]); }); const _updateDatas = newRowDatasList.map((item, index) => { From a8e6947319887f1cee559af4791d892c6e5e9488 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Mon, 13 Nov 2023 14:43:10 +0800 Subject: [PATCH 045/126] console update --- .../saved/request/OperationUpdateRequest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationUpdateRequest.java index 67a681e6f..5b0b9fe21 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationUpdateRequest.java @@ -23,6 +23,26 @@ public class OperationUpdateRequest { */ private String name; + /** + * 数据源连接ID + */ + private Long dataSourceId; + + /** + * db名称 + */ + private String databaseName; + + /** + * 表所在空间 + */ + private String schemaName; + + /** + * 数据库类型 + */ + private String type; + /** * ddl内容 */ From 78df03bda5cf2ff711ca20c37fbffdccfc937cdc Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 13 Nov 2023 17:10:37 +0800 Subject: [PATCH 046/126] refactor-workspace-tabs --- CHANGELOG.md | 9 + CHANGELOG_CN.md | 9 + .../src/blocks/SQLExecute/index.less | 2 +- .../src/blocks/SQLExecute/index.tsx | 134 ++++++------- .../blocks/Tree/TreeNodeRightClick/index.tsx | 2 +- .../ChatInput/index.less | 0 .../ChatInput/index.tsx | 0 .../MonacoEditor/index.less | 0 .../MonacoEditor/index.tsx | 0 .../MonacoEditor/monacoEditorConfig.ts | 2 +- .../MonacoEditor/syntax-parser/index.ts | 0 .../MonacoEditor/syntax-parser/lexer/index.ts | 0 .../MonacoEditor/syntax-parser/lexer/token.ts | 0 .../syntax-parser/parser/chain.ts | 0 .../syntax-parser/parser/define.ts | 0 .../syntax-parser/parser/index.ts | 0 .../syntax-parser/parser/match.ts | 0 .../syntax-parser/parser/scanner.ts | 0 .../syntax-parser/parser/utils.ts | 0 .../plugin/monaco-plugin/default-opts.ts | 0 .../plugin/monaco-plugin/index.ts | 0 .../plugin/monaco-plugin/parser.worker.ts | 0 .../plugin/sql-parser/base/define.ts | 0 .../plugin/sql-parser/base/four-operations.ts | 0 .../plugin/sql-parser/base/parser.ts | 0 .../plugin/sql-parser/base/reader.ts | 0 .../plugin/sql-parser/base/reserve-keys.ts | 0 .../plugin/sql-parser/base/utils.ts | 0 .../syntax-parser/plugin/sql-parser/index.tsx | 0 .../plugin/sql-parser/mysql/index.ts | 0 .../plugin/sql-parser/mysql/lexer.ts | 0 .../plugin/sql-parser/mysql/parser.ts | 0 .../MonacoEditor/useMonacoTheme.ts | 0 .../NewMonacoEditor/index.less | 0 .../NewMonacoEditor/index.tsx | 0 .../{Console => ConsoleEditor}/index.less | 0 .../{Console => ConsoleEditor}/index.tsx | 1 - .../src/components/CreateDatabase/index.tsx | 2 +- .../src/components/EditDialog/index.tsx | 2 +- .../src/components/ExecuteSQL/index.tsx | 2 +- .../components/TableBox/index.tsx | 4 +- chat2db-client/src/hooks/useCreateConsole.ts | 51 +++++ chat2db-client/src/hooks/useGetConsoleList.ts | 21 -- .../pages/main/dashboard/chart-item/index.tsx | 2 +- .../components/WorkspaceTabs/index.less | 5 + .../components/WorkspaceTabs/index.tsx | 183 +++++++++++++++--- .../src/pages/main/workspace/index.tsx | 2 +- chat2db-client/src/pages/test/index.tsx | 2 +- chat2db-client/src/service/history.ts | 4 +- chat2db-client/src/store/console/index.ts | 13 +- chat2db-client/src/store/workspace/common.ts | 1 - chat2db-client/src/typings/common.ts | 6 +- .../saved/request/OperationUpdateRequest.java | 20 ++ 53 files changed, 339 insertions(+), 140 deletions(-) rename chat2db-client/src/components/{Console => ConsoleEditor}/ChatInput/index.less (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/ChatInput/index.tsx (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/index.less (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/index.tsx (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/monacoEditorConfig.ts (93%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/index.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/lexer/index.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/lexer/token.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/parser/chain.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/parser/define.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/parser/index.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/parser/match.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/parser/scanner.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/parser/utils.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/MonacoEditor/useMonacoTheme.ts (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/NewMonacoEditor/index.less (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/NewMonacoEditor/index.tsx (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/index.less (100%) rename chat2db-client/src/components/{Console => ConsoleEditor}/index.tsx (99%) create mode 100644 chat2db-client/src/hooks/useCreateConsole.ts delete mode 100644 chat2db-client/src/hooks/useGetConsoleList.ts create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f64d6d1c..63e1e6b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 3.0.12 + +`2023-11-13` + +**更新日志** + +- 🐞【Fixed】Copy as insert first row lost problem + + ## 3.0.11 `2023-11-08` diff --git a/CHANGELOG_CN.md b/CHANGELOG_CN.md index e0df3f2e1..4525f85d9 100644 --- a/CHANGELOG_CN.md +++ b/CHANGELOG_CN.md @@ -1,3 +1,12 @@ +## 3.0.12 + +`2023-11-13` + +**更新日志** + +- 🐞【修复】复制为insert第一行丢失问题 + + ## 3.0.11 `2023-11-10` diff --git a/chat2db-client/src/blocks/SQLExecute/index.less b/chat2db-client/src/blocks/SQLExecute/index.less index c2ef8c354..70cc2cbff 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.less +++ b/chat2db-client/src/blocks/SQLExecute/index.less @@ -1,6 +1,6 @@ @import '../../styles/var.less'; -.box { +.sqlExecute { height: 100%; } diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 9a26c7dd7..1f9c79a90 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -1,90 +1,92 @@ -import React, { memo, useRef } from 'react'; -import { connect } from 'umi'; +import React, { memo, useRef, createContext } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; -import Console, { IConsoleRef } from '@/components/Console'; +import ConsoleEditor, { IConsoleRef } from '@/components/ConsoleEditor'; import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; -import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; -import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; -import { IAIState } from '@/models/ai'; +import { DatabaseTypeCode } from '@/constants'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IProps { - className?: string; - isActive: boolean; - workspaceModel: IWorkspaceModelState; - aiModel: IAIState; - dispatch: any; - data: { + boundInfo: { databaseName: string; dataSourceId: number; type: DatabaseTypeCode; schemaName?: string; - consoleId: number; - consoleName: string; - initDDL: string; - status?: ConsoleStatus; }; + initDDL: string; + consoleId: number; } +interface IContext { + boundInfo: { + databaseName: string; + dataSourceId: number; + type: DatabaseTypeCode; + schemaName?: string; + }; + consoleId: number; +} + +export const SQLExecuteContext = createContext({} as any); + const SQLExecute = memo((props) => { - const { data, workspaceModel, aiModel, isActive, dispatch } = props; + const { boundInfo, initDDL } = props; const draggableRef = useRef(); - const { curTableList, curWorkspaceParams } = workspaceModel; - // const [sql, setSql] = useState(''); const searchResultRef = useRef(null); const consoleRef = useRef(null); useUpdateEffect(() => { - consoleRef.current?.editorRef?.setValue(data.initDDL, 'cover'); - }, [data.initDDL]); + consoleRef.current?.editorRef?.setValue(initDDL, 'cover'); + }, [initDDL]); return ( -
- -
- { - searchResultRef.current?.handleExecuteSQL(sql); - }} - onConsoleSave={() => { - dispatch({ - type: 'workspace/fetchGetSavedConsole', - payload: { - status: ConsoleStatus.RELEASE, - orderByDesc: true, - ...curWorkspaceParams, - }, - callback: (res: any) => { - dispatch({ - type: 'workspace/setConsoleList', - payload: res.data, - }); - }, - }); - }} - tables={curTableList || []} - remainingUse={aiModel.remainingUse} - /> -
-
- -
-
-
+ +
+ +
+ { + searchResultRef.current?.handleExecuteSQL(sql); + }} + // isActive={isActive} + // onConsoleSave={() => { + // dispatch({ + // type: 'workspace/fetchGetSavedConsole', + // payload: { + // status: ConsoleStatus.RELEASE, + // orderByDesc: true, + // ...curWorkspaceParams, + // }, + // callback: (res: any) => { + // dispatch({ + // type: 'workspace/setConsoleList', + // payload: res.data, + // }); + // }, + // }); + // }} + // tables={curTableList || []} + // remainingUse={aiModel.remainingUse} + /> +
+
+ +
+
+
+
); }); -const dvaModel = connect(({ workspace, ai }: { workspace: IWorkspaceModelType; ai: IAIState }) => ({ - workspaceModel: workspace, - aiModel: ai, -})); - -export default dvaModel(SQLExecute); +export default SQLExecute; diff --git a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx index cd52e1ccc..38b0d7b3a 100644 --- a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx @@ -7,7 +7,7 @@ import styles from './index.less'; // ----- components ----- import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; -import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; +import MonacoEditor, { IExportRefFunction } from '@/components/ConsoleEditor/MonacoEditor'; import MenuLabel from '@/components/MenuLabel'; // ----- constants ----- diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/ConsoleEditor/ChatInput/index.less similarity index 100% rename from chat2db-client/src/components/Console/ChatInput/index.less rename to chat2db-client/src/components/ConsoleEditor/ChatInput/index.less diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/ConsoleEditor/ChatInput/index.tsx similarity index 100% rename from chat2db-client/src/components/Console/ChatInput/index.tsx rename to chat2db-client/src/components/ConsoleEditor/ChatInput/index.tsx diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.less b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/index.less similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/index.less rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/index.less diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/index.tsx similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/index.tsx rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/index.tsx diff --git a/chat2db-client/src/components/Console/MonacoEditor/monacoEditorConfig.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/monacoEditorConfig.ts similarity index 93% rename from chat2db-client/src/components/Console/MonacoEditor/monacoEditorConfig.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/monacoEditorConfig.ts index 2d73eabcd..f8cec3ec7 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/monacoEditorConfig.ts +++ b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/monacoEditorConfig.ts @@ -1,4 +1,4 @@ -import { IEditorOptions } from '@/components/Console/MonacoEditor'; +import { IEditorOptions } from '@/components/ConsoleEditor/MonacoEditor'; export const editorDefaultOptions: IEditorOptions = { fontFamily: `"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas", "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace`, diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/index.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/index.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/index.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/index.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/index.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/lexer/index.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/index.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/lexer/index.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/token.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/lexer/token.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/token.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/lexer/token.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/chain.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/chain.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/chain.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/chain.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/define.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/define.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/define.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/define.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/index.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/index.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/index.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/index.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/match.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/match.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/match.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/match.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/scanner.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/scanner.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/scanner.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/scanner.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/utils.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/utils.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/utils.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/parser/utils.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts diff --git a/chat2db-client/src/components/Console/MonacoEditor/useMonacoTheme.ts b/chat2db-client/src/components/ConsoleEditor/MonacoEditor/useMonacoTheme.ts similarity index 100% rename from chat2db-client/src/components/Console/MonacoEditor/useMonacoTheme.ts rename to chat2db-client/src/components/ConsoleEditor/MonacoEditor/useMonacoTheme.ts diff --git a/chat2db-client/src/components/Console/NewMonacoEditor/index.less b/chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.less similarity index 100% rename from chat2db-client/src/components/Console/NewMonacoEditor/index.less rename to chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.less diff --git a/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx b/chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.tsx similarity index 100% rename from chat2db-client/src/components/Console/NewMonacoEditor/index.tsx rename to chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.tsx diff --git a/chat2db-client/src/components/Console/index.less b/chat2db-client/src/components/ConsoleEditor/index.less similarity index 100% rename from chat2db-client/src/components/Console/index.less rename to chat2db-client/src/components/ConsoleEditor/index.less diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/ConsoleEditor/index.tsx similarity index 99% rename from chat2db-client/src/components/Console/index.tsx rename to chat2db-client/src/components/ConsoleEditor/index.tsx index 4e122edd9..155214067 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/index.tsx @@ -582,7 +582,6 @@ function Console(props: IProps, ref: ForwardedRef) { -
+
+ {i18n('common.text.contactUs')}: + window.open('https://github.com/chat2db/Chat2DB')} /> + } + > + + +
+
+ ); + } + + return ( + +
+ +
+ + +
+ ); +}; + +export default GlobalLayout; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index2.tsx similarity index 81% rename from chat2db-client/src/layouts/index.tsx rename to chat2db-client/src/layouts/index2.tsx index 2b8e1b2c2..e076cffdf 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index2.tsx @@ -4,45 +4,19 @@ import { Outlet } from 'umi'; import { ConfigProvider, Spin } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { getAntdThemeConfig } from '@/theme'; -import { IVersionResponse } from '@/typings'; import miscService from '@/service/misc'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import { useTheme } from '@/hooks'; import { ThemeType, LangType, PrimaryColorType } from '@/constants/'; -import styles from './index.less'; import { getLang, setLang } from '@/utils/localStorage'; import { clearOlderLocalStorage } from '@/utils'; import registerMessage from './init/registerMessage'; import registerNotification from './init/registerNotification'; import MyNotification from '@/components/MyNotification'; -// import Iconfont from '@/components/Iconfont'; -// import Setting from '@/blocks/Setting'; import indexedDB from '@/indexedDB'; import useCopyFocusData from '@/hooks/useFocusData'; - -declare global { - interface Window { - _Lang: string; - _APP_PORT: string; - _BUILD_TIME: string; - _BaseURL: string; - _AppThemePack: { [key in string]: string }; - _appGatewayParams: IVersionResponse; - _notificationApi: any; - _indexedDB: any; - electronApi?: { - startServerForSpawn: () => void; - quitApp: () => void; - setBaseURL: (baseUrl: string) => void; - registerAppMenu: (data: any) => void; - }; - } - const __APP_VERSION__: string; - const __BUILD_TIME__: string; - const __ENV__: string; - const __APP_PORT__: string; -} +import styles from './index.less'; const initConfig = () => { registerMessage(); @@ -139,16 +113,7 @@ function AppContainer() { }; } - // 初始化语言 - function initLang() { - const lang = getLang(); - if (!lang) { - setLang(LangType.EN_US); - document.documentElement.setAttribute('lang', LangType.EN_US); - const date = new Date('2030-12-30 12:30:00').toUTCString(); - document.cookie = `CHAT2DB.LOCALE=${lang};Expires=${date}`; - } - } + useEffect(() => { detectionService(); @@ -203,8 +168,7 @@ function AppContainer() { {startSchedule === 2 && }
)} - {/* 全局的弹窗 */} - +
); } diff --git a/chat2db-client/src/layouts/init/init.ts b/chat2db-client/src/layouts/init/init.ts new file mode 100644 index 000000000..edf728efd --- /dev/null +++ b/chat2db-client/src/layouts/init/init.ts @@ -0,0 +1,30 @@ +import { clearOlderLocalStorage } from '@/utils'; +import initIndexedDB from './initIndexedDB'; +import registerElectronApi from './registerElectronApi'; +import registerMessage from './registerMessage'; +import registerNotification from './registerNotification'; +import { getLang, setLang } from '@/utils/localStorage'; +import { LangType } from '@/constants'; + +const init = () => { + clearOlderLocalStorage(); + + initLang(); + initIndexedDB(); + registerElectronApi(); + + registerMessage(); + registerNotification(); +}; + +// 初始化语言 +const initLang = () => { + const lang = getLang(); + if (!lang) { + setLang(LangType.EN_US); + document.documentElement.setAttribute('lang', LangType.EN_US); + const date = new Date('2030-12-30 12:30:00').toUTCString(); + document.cookie = `CHAT2DB.LOCALE=${lang};Expires=${date}`; + } +}; +export default init; diff --git a/chat2db-client/src/layouts/init/initIndexedDB.ts b/chat2db-client/src/layouts/init/initIndexedDB.ts new file mode 100644 index 000000000..3fe8d643d --- /dev/null +++ b/chat2db-client/src/layouts/init/initIndexedDB.ts @@ -0,0 +1,12 @@ +import indexedDB from '@/indexedDB'; + +/** 初始化indexedDB */ +const initIndexedDB = () => { + indexedDB.createDB('chat2db', 1).then((db) => { + window._indexedDB = { + chat2db: db, + }; + }); +}; + +export default initIndexedDB; diff --git a/chat2db-client/src/layouts/init/registerElectronApi.ts b/chat2db-client/src/layouts/init/registerElectronApi.ts new file mode 100644 index 000000000..bdc99eb06 --- /dev/null +++ b/chat2db-client/src/layouts/init/registerElectronApi.ts @@ -0,0 +1,9 @@ +// 注册Electron关闭时,关闭服务 +const registerElectronApi = () => { + window.electronApi?.registerAppMenu({ + version: __APP_VERSION__, + }); + window.electronApi?.setBaseURL?.(window._BaseURL); +}; + +export default registerElectronApi; diff --git a/chat2db-client/src/service/misc.tsx b/chat2db-client/src/service/misc.tsx index 22860ad8c..b1fe34bc6 100644 --- a/chat2db-client/src/service/misc.tsx +++ b/chat2db-client/src/service/misc.tsx @@ -1,5 +1,5 @@ import createRequest from "./base"; -const testService = createRequest('/api/system', { errorLevel: false }); +const testService = createRequest('/api/system', { errorLevel: false }); const systemStop = createRequest('/api/system/stop', { errorLevel: false, method: 'post' }); const testApiSmooth = createRequest('/api/system/get-version-a', { errorLevel: false, method: 'get' }); diff --git a/chat2db-client/tsconfig.json b/chat2db-client/tsconfig.json index bfe6ef82f..4ada9d3e3 100644 --- a/chat2db-client/tsconfig.json +++ b/chat2db-client/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "moduleResolution": "node", "jsx": "react-jsx", - "noImplicitAny": false, - }, -} \ No newline at end of file + "noImplicitAny": false + } +} diff --git a/chat2db-client/typings.d.ts b/chat2db-client/typings.d.ts index e076dda6e..4d6d1e725 100644 --- a/chat2db-client/typings.d.ts +++ b/chat2db-client/typings.d.ts @@ -1,9 +1,36 @@ import 'umi/typings'; +import { IVersionResponse } from '@/typings'; +declare module 'monaco-editor/esm/vs/basic-languages/sql/sql'; +declare module 'monaco-editor/esm/vs/language/typescript/ts.worker.js'; +declare module 'monaco-editor/esm/vs/editor/editor.worker.js'; declare namespace NodeJS { interface ProcessEnv { readonly NODE_ENV: 'development' | 'production' readonly UMI_ENV: string readonly __ENV: string; } -} \ No newline at end of file +} + +declare global { + interface Window { + _Lang: string; + _APP_PORT: string; + _BUILD_TIME: string; + _BaseURL: string; + _AppThemePack: { [key in string]: string }; + _appGatewayParams: IVersionResponse; + _notificationApi: any; + _indexedDB: any; + electronApi?: { + startServerForSpawn: () => void; + quitApp: () => void; + setBaseURL: (baseUrl: string) => void; + registerAppMenu: (data: any) => void; + }; + } + const __APP_VERSION__: string; + const __BUILD_TIME__: string; + const __ENV__: string; + const __APP_PORT__: string; +} From 9b6b564a1925c7df428467ef4d376a1a280a06d9 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 13 Nov 2023 18:10:13 +0800 Subject: [PATCH 048/126] Fix DM index error --- .../src/main/java/ai/chat2db/plugin/dm/DMMetaData.java | 2 +- .../controller/operation/saved/OperationSavedController.java | 3 +-- .../src/main/java/ai/chat2db/spi/sql/IDriverManager.java | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java index eafad8731..d5b750a0a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java @@ -145,7 +145,7 @@ public Table view(Connection connection, String databaseName, String schemaName, }); } - private static String INDEX_SQL = "SELECT i.TABLE_NAME, i.INDEX_TYPE, i.INDEX_NAME, i.UNIQUENESS ,c.COLUMN_NAME, c.COLUMN_POSITION, c.DESCEND, cons.CONSTRAINT_TYPE FROM ALL_INDEXES i JOIN ALL_IND_COLUMNS c ON i.INDEX_NAME = c.INDEX_NAME AND i.TABLE_NAME = c.TABLE_NAME AND i.TABLE_OWNER = c.TABLE_OWNER LEFT JOIN ALL_CONSTRAINTS cons ON i.INDEX_NAME = cons.INDEX_NAME AND i.TABLE_NAME = cons.TABLE_NAME AND i.TABLE_OWNER = cons.OWNER WHERE i.TABLE_NAME = '%s' ORDER BY i.INDEX_NAME, c.COLUMN_POSITION;"; + private static String INDEX_SQL = "SELECT i.TABLE_NAME, i.INDEX_TYPE, i.INDEX_NAME, i.UNIQUENESS ,c.COLUMN_NAME, c.COLUMN_POSITION, c.DESCEND, cons.CONSTRAINT_TYPE FROM ALL_INDEXES i JOIN ALL_IND_COLUMNS c ON i.INDEX_NAME = c.INDEX_NAME AND i.TABLE_NAME = c.TABLE_NAME AND i.TABLE_OWNER = c.TABLE_OWNER LEFT JOIN ALL_CONSTRAINTS cons ON i.INDEX_NAME = cons.INDEX_NAME AND i.TABLE_NAME = cons.TABLE_NAME AND i.TABLE_OWNER = cons.OWNER WHERE i.TABLE_OWNER = '%s' AND i.TABLE_NAME = '%s' ORDER BY i.INDEX_NAME, c.COLUMN_POSITION;"; @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java index c63d41a9e..42b2adc7d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/OperationSavedController.java @@ -73,8 +73,7 @@ public DataResult get(@PathVariable("id") Long id) { OperationQueryParam param = new OperationQueryParam(); param.setId(id); param.setUserId(ContextUtils.getUserId()); - return operationService.queryExistent(param) - .map(operationWebConverter::dto2vo); + return operationService.queryExistent(param).map(operationWebConverter::dto2vo); } /** diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java index 1d1862d29..383193e49 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java @@ -85,8 +85,9 @@ public static Connection getConnection(String url, Properties info, DriverConfig if (Objects.isNull(driverEntry)) { driverEntry = getJDBCDriver(driver); } - - try (Connection connection = driverEntry.getDriver().connect(url, info)) { + Connection connection; + try { + connection = driverEntry.getDriver().connect(url, info); if (Objects.isNull(connection)) { throw new SQLException(String.format("driver.connect return null , No suitable driver found for url %s", url), SQL_STATE_CODE); From a0187de02d828fec40a65481cb0f17a9c7193003 Mon Sep 17 00:00:00 2001 From: tmlx1990 Date: Mon, 13 Nov 2023 18:23:41 +0800 Subject: [PATCH 049/126] =?UTF-8?q?=E5=B0=86=E5=AD=97=E6=AE=B5=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=80=BC=E9=80=89=E9=A1=B9=E6=94=B9=E4=B8=BA=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E4=BC=A0=E9=80=92=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DatabaseTableEditor/ColumnList/index.tsx | 17 +---------- .../src/blocks/DatabaseTableEditor/index.tsx | 11 +++++++ chat2db-client/src/typings/database.ts | 6 ++++ .../chat2db/plugin/mysql/MysqlMetaData.java | 6 ++-- .../mysql/type/MysqlDefaultValueEnum.java | 29 +++++++++++++++++++ .../chat2db/plugin/oracle/OracleMetaData.java | 2 ++ .../oracle/type/OracleDefaultValueEnum.java | 28 ++++++++++++++++++ .../ai/chat2db/spi/model/DefaultValue.java | 12 ++++++++ .../java/ai/chat2db/spi/model/TableMeta.java | 2 ++ 9 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlDefaultValueEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleDefaultValueEnum.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DefaultValue.java diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index b013e8f78..ad6b6718f 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -503,22 +503,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) )} {editingConfig?.supportDefaultValue && ( - + )} {editingConfig?.supportCharset && ( diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 8da03ecfc..4638b3949 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -54,6 +54,7 @@ export interface IDatabaseSupportField { charsets: IOption[]; collations: IOption[]; indexTypes: IOption[]; + defaultValues: IOption[]; } export default memo((props: IProps) => { @@ -93,6 +94,7 @@ export default memo((props: IProps) => { charsets: [], collations: [], indexTypes: [], + defaultValues: [], }); const [isLoading, setIsLoading] = useState(false); @@ -148,11 +150,20 @@ export default memo((props: IProps) => { }; }) || []; + const defaultValues = + res?.defaultValues?.map((i) => { + return { + value: i.defaultValue, + label: i.defaultValue, + }; + }) || []; + setDatabaseSupportField({ columnTypes, charsets, collations, indexTypes, + defaultValues, }); }); } diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index b17604298..a99ff117e 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -51,6 +51,7 @@ export interface IDatabaseSupportField { charsets: ICharset[]; collations: ICollation[]; indexTypes: IIndexTypes[]; + defaultValues: IDefaultValue[]; } /** 字段所对应的 字符集*/ @@ -84,3 +85,8 @@ export interface IColumnTypes { supportValue: boolean; // 是否支持值 supportUnit: boolean; // 是否支持单位 } + +/** 不同数据库支持不同的默认值 */ +export interface IDefaultValue { + defaultValue: string; // 默认值 +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java index 27c03f31b..edd32797b 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java @@ -8,10 +8,7 @@ import java.util.stream.Stream; import ai.chat2db.plugin.mysql.builder.MysqlSqlBuilder; -import ai.chat2db.plugin.mysql.type.MysqlCharsetEnum; -import ai.chat2db.plugin.mysql.type.MysqlCollationEnum; -import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; -import ai.chat2db.plugin.mysql.type.MysqlIndexTypeEnum; +import ai.chat2db.plugin.mysql.type.*; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; @@ -287,6 +284,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .charsets(MysqlCharsetEnum.getCharsets()) .collations(MysqlCollationEnum.getCollations()) .indexTypes(MysqlIndexTypeEnum.getIndexTypes()) + .defaultValues(MysqlDefaultValueEnum.getDefaultValues()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlDefaultValueEnum.java new file mode 100644 index 000000000..b48e2c216 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlDefaultValueEnum.java @@ -0,0 +1,29 @@ +package ai.chat2db.plugin.mysql.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum MysqlDefaultValueEnum { + + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + CURRENT_TIMESTAMP("CURRENT_TIMESTAMP"), + ; + private DefaultValue defaultValue; + + MysqlDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(MysqlDefaultValueEnum.values()).map(MysqlDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java index e2cca8ff1..866943326 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleMetaData.java @@ -8,6 +8,7 @@ import ai.chat2db.plugin.oracle.builder.OracleSqlBuilder; import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; +import ai.chat2db.plugin.oracle.type.OracleDefaultValueEnum; import ai.chat2db.plugin.oracle.type.OracleIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -290,6 +291,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .charsets(Lists.newArrayList()) .collations(Lists.newArrayList()) .indexTypes(OracleIndexTypeEnum.getIndexTypes()) + .defaultValues(OracleDefaultValueEnum.getDefaultValues()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleDefaultValueEnum.java new file mode 100644 index 000000000..473af5ed8 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleDefaultValueEnum.java @@ -0,0 +1,28 @@ +package ai.chat2db.plugin.oracle.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum OracleDefaultValueEnum { + + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + OracleDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(OracleDefaultValueEnum.values()).map(OracleDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DefaultValue.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DefaultValue.java new file mode 100644 index 000000000..dfffbf164 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DefaultValue.java @@ -0,0 +1,12 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class DefaultValue { + + private String defaultValue; + +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java index 82e9a5a95..6ae0c2d7f 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java @@ -19,4 +19,6 @@ public class TableMeta { private List indexTypes; + + private List defaultValues; } From 26c0e51b7b2728a0a764e9d94990ecdbc843b05d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 13 Nov 2023 20:32:14 +0800 Subject: [PATCH 050/126] refactor: file structure --- chat2db-client/.umirc.ts | 7 +- chat2db-client/src/hooks/useTheme.ts | 33 +++- .../src/layouts/BasicLayout/index.less | 0 .../src/layouts/BasicLayout/index.tsx | 8 - .../src/layouts/GlobalLayout/index.tsx | 35 +++- chat2db-client/src/layouts/index.less | 104 ----------- chat2db-client/src/layouts/index2.tsx | 174 ------------------ chat2db-client/src/layouts/init/init.ts | 1 + chat2db-client/src/typings/theme.ts | 2 +- 9 files changed, 60 insertions(+), 304 deletions(-) delete mode 100644 chat2db-client/src/layouts/BasicLayout/index.less delete mode 100644 chat2db-client/src/layouts/BasicLayout/index.tsx delete mode 100644 chat2db-client/src/layouts/index.less delete mode 100644 chat2db-client/src/layouts/index2.tsx diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index dd18ac841..2326430a7 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -23,7 +23,12 @@ export default defineConfig({ { path: '/', component: '@/layouts/GlobalLayout', - routes: [{ path: '/', component: 'main' }], + routes: [ + { + path: '/', + component: 'main', + }, + ], }, ], diff --git a/chat2db-client/src/hooks/useTheme.ts b/chat2db-client/src/hooks/useTheme.ts index a4b466c13..cea106e76 100644 --- a/chat2db-client/src/hooks/useTheme.ts +++ b/chat2db-client/src/hooks/useTheme.ts @@ -1,21 +1,34 @@ import { useEffect, useState } from 'react'; -// import { addColorSchemeListener, colorSchemeListeners } from '@/layouts'; +import { addColorSchemeListener, colorSchemeListeners } from '@/layouts/GlobalLayout'; import { getOsTheme } from '@/utils'; import { ITheme } from '@/typings'; import { ThemeType, PrimaryColorType } from '@/constants'; import { getPrimaryColor, getTheme, setPrimaryColor, setTheme } from '@/utils/localStorage'; +import { v4 as uuidv4 } from 'uuid'; + +const colorSchemeListeners: { + [key: string]: (theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) => void; +} = {}; + +const addColorSchemeListener = ( + callback: (theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) => void, +) => { + const uuid = uuidv4(); + colorSchemeListeners[uuid] = callback; + return uuid; +}; const initialTheme = () => { const localStorageTheme = getTheme(); const localStoragePrimaryColor = getPrimaryColor(); // 判断localStorage的theme在不在ThemeType中, 如果存在就用localStorageTheme - let backgroundColor = ThemeType.Light + let backgroundColor = ThemeType.Light; if (Object.values(ThemeType).includes(localStorageTheme)) { backgroundColor = localStorageTheme; } - let primaryColor = PrimaryColorType.Golden_Purple + let primaryColor = PrimaryColorType.Golden_Purple; if (Object.values(PrimaryColorType).includes(localStoragePrimaryColor)) { primaryColor = localStoragePrimaryColor; } @@ -37,10 +50,10 @@ export function useTheme(): [T, React.Dispatch appTheme.backgroundColor === ThemeType.Dark, [appTheme]); useEffect(() => { - // const uuid = addColorSchemeListener(setAppTheme as any); - // return () => { - // delete colorSchemeListeners[uuid]; - // }; + const uuid = addColorSchemeListener(setAppTheme as any); + return () => { + delete colorSchemeListeners[uuid]; + }; }, []); function handleAppThemeChange(theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) { @@ -50,9 +63,9 @@ export function useTheme(): [T, React.Dispatch { - // colorSchemeListeners[t]?.(theme); - // }); + Object.keys(colorSchemeListeners)?.forEach((t) => { + colorSchemeListeners[t]?.(theme); + }); document.documentElement.setAttribute('theme', theme.backgroundColor); setTheme(theme.backgroundColor); document.documentElement.setAttribute('primary-color', theme.primaryColor); diff --git a/chat2db-client/src/layouts/BasicLayout/index.less b/chat2db-client/src/layouts/BasicLayout/index.less deleted file mode 100644 index e69de29bb..000000000 diff --git a/chat2db-client/src/layouts/BasicLayout/index.tsx b/chat2db-client/src/layouts/BasicLayout/index.tsx deleted file mode 100644 index f17e67c5e..000000000 --- a/chat2db-client/src/layouts/BasicLayout/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import styles from './index.less'; - -function BasicLayout(props) { - return
{props.children}
; -} - -export default BasicLayout; diff --git a/chat2db-client/src/layouts/GlobalLayout/index.tsx b/chat2db-client/src/layouts/GlobalLayout/index.tsx index 6c21629d5..6d366db75 100644 --- a/chat2db-client/src/layouts/GlobalLayout/index.tsx +++ b/chat2db-client/src/layouts/GlobalLayout/index.tsx @@ -1,30 +1,53 @@ -import React, { useEffect } from 'react'; +import React, { useLayoutEffect, useState } from 'react'; import usePollRequestService, { ServiceStatus } from '@/hooks/usePollRequestService'; import i18n, { isEn } from '@/i18n'; import { Button, ConfigProvider, Spin, Tooltip } from 'antd'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import service from '@/service/misc'; -import styles from './index.less'; import MyNotification from '@/components/MyNotification'; +import useCopyFocusData from '@/hooks/useFocusData'; import { useTheme } from '@/hooks/useTheme'; import { getAntdThemeConfig } from '@/theme'; -import useCopyFocusData from '@/hooks/useFocusData'; import { Outlet } from 'umi'; import init from '../init/init'; import { GithubOutlined, SyncOutlined, WechatOutlined } from '@ant-design/icons'; +import { ThemeType } from '@/constants'; +import styles from './index.less'; const GlobalLayout = () => { - const [appTheme] = useTheme(); + const [appTheme, setAppTheme] = useTheme(); + const [antdTheme, setAntdTheme] = useState({}); + const { serviceStatus, restartPolling } = usePollRequestService({ loopService: service.testService, }); + useCopyFocusData(); - useEffect(() => { + useLayoutEffect(() => { + setAntdTheme(getAntdThemeConfig(appTheme)); + }, [appTheme]); + + useLayoutEffect(() => { init(); + monitorOsTheme(); }, []); + // 监听系统(OS)主题变化 + const monitorOsTheme = () => { + function change(e: any) { + if (appTheme.backgroundColor === ThemeType.FollowOs) { + setAppTheme({ + ...appTheme, + backgroundColor: e.matches ? ThemeType.Dark : ThemeType.Light, + }); + } + } + const matchMedia = window.matchMedia('(prefers-color-scheme: dark)'); + matchMedia.onchange = change; + }; + // 等待状态页面 if (serviceStatus === ServiceStatus.PENDING) { return ; @@ -53,7 +76,7 @@ const GlobalLayout = () => { } return ( - +
diff --git a/chat2db-client/src/layouts/index.less b/chat2db-client/src/layouts/index.less deleted file mode 100644 index 4251c65ff..000000000 --- a/chat2db-client/src/layouts/index.less +++ /dev/null @@ -1,104 +0,0 @@ -@import '../styles/global.less'; -@import '../styles/var.less'; - -:global { - #root { - height: 100%; - - .codicon-symbol-text:before { - content: '\eb11'; - width: 16px; - height: 16px; - } - - .codicon-symbol-function:before { - content: '\ebb8'; - width: 16px; - height: 16px; - // background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAKZJREFUOE+lkgEOgzAMA83LNl7G9rJtL2O6qkZZlRYGkSpacGzHdNLFmi72a0TwkLQEgRTbI3DzLOk9ctkjWCU9JUE0rBHBoXwi6BWk7tX6p7rgTDEOe1ZxFwkMIjgaPTtPwLc6FkLbeJlNAFaO8/MekZ9sMgICzNL/i6AltivGYb8JtED//zYbcqGJch7lnBEYtHcFyvee1d0LZPaMwFZPOTjUFEFfU0IlESizFzUAAAAASUVORK5CYII="); - } - - .codicon-symbol-folder:before { - content: '\ebb7'; - width: 16px; - height: 16px; - // background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAHxJREFUOE/Fk10OgCAMgz9Opp5MPZl4Mk0JS4AEJWK0L+OvZWzF0QkX+SMwA4ot8MAKeBPYgB1YWtjx3ABMJnAANm7UIHBeFWi9OT1XzcBqUYsSuXzCPwLqq0EtEtRaoZxrTb7JatAtkPrg+xqUVr7LQPuZlbs/0xMXBs4Jp5IvERhfiDgAAAAASUVORK5CYII="); - } - - .codicon-symbol-field:before { - content: '\eb17'; - width: 16px; - height: 16px; - // background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4='); - } - .codicon-symbol-unit:before { - content: '\ea70'; - width: 16px; - height: 16px; - // background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4='); - } - .codicon-symbol-property:before { - content: '\eace'; - width: 16px; - height: 16px; - // background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4='); - } - } -} - -.app { - min-height: 100%; -} - -.appContainer { - min-height: 100%; - background-color: var(--color-bg-base); -} - -.loadingBox { - height: 100vh; - width: 100vw; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.github { - font-size: 18px; -} - -.restart { - cursor: pointer; - font-size: 18px; - margin-top: 10px; - - &:hover { - color: var(--color-primary); - } -} - -.hint { - transform: translateY(120px); -} - -.settingBox { - position: absolute; - top: 20px; - right: 30px; - height: 32px; - width: 32px; - display: flex; - justify-content: center; - align-items: center; - - &:hover { - i { - color: var(--color-primary); - } - } - - i { - font-size: 16px; - } -} \ No newline at end of file diff --git a/chat2db-client/src/layouts/index2.tsx b/chat2db-client/src/layouts/index2.tsx deleted file mode 100644 index e076cffdf..000000000 --- a/chat2db-client/src/layouts/index2.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { useEffect, useLayoutEffect, useState } from 'react'; -import i18n, { isEn } from '@/i18n'; -import { Outlet } from 'umi'; -import { ConfigProvider, Spin } from 'antd'; -import { v4 as uuidv4 } from 'uuid'; -import { getAntdThemeConfig } from '@/theme'; -import miscService from '@/service/misc'; -import antdEnUS from 'antd/locale/en_US'; -import antdZhCN from 'antd/locale/zh_CN'; -import { useTheme } from '@/hooks'; -import { ThemeType, LangType, PrimaryColorType } from '@/constants/'; -import { getLang, setLang } from '@/utils/localStorage'; -import { clearOlderLocalStorage } from '@/utils'; -import registerMessage from './init/registerMessage'; -import registerNotification from './init/registerNotification'; -import MyNotification from '@/components/MyNotification'; -import indexedDB from '@/indexedDB'; -import useCopyFocusData from '@/hooks/useFocusData'; -import styles from './index.less'; - -const initConfig = () => { - registerMessage(); - registerNotification(); - clearOlderLocalStorage(); -}; - -initConfig(); - -window._Lang = getLang(); - -export const colorSchemeListeners: { - [key: string]: (theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) => void; -} = {}; - -export function addColorSchemeListener( - callback: (theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) => void, -) { - const uuid = uuidv4(); - colorSchemeListeners[uuid] = callback; - return uuid; -} - -export default function Layout() { - const [appTheme] = useTheme(); - const [antdTheme, setAntdTheme] = useState({}); - - useLayoutEffect(() => { - setAntdTheme(getAntdThemeConfig(appTheme)); - }, [appTheme]); - - return ( - - - - ); -} - -/** 重启次数 */ -const restartCount = 200; - -function AppContainer() { - const [initEnd, setInitEnd] = useState(false); - const [appTheme, setAppTheme] = useTheme(); - const [startSchedule, setStartSchedule] = useState(0); // 0 初始状态 1 服务启动中 2 启动成功 - const [serviceFail, setServiceFail] = useState(false); - useCopyFocusData(); - - useLayoutEffect(() => { - collectInitApp(); - }, []); - - // 初始化app - function collectInitApp() { - registerElectronApi(); - monitorOsTheme(); - initLang(); - initIndexedDB(); - setInitEnd(true); - } - - // 注册Electron关闭时,关闭服务 - function registerElectronApi() { - window.electronApi?.registerAppMenu({ - version: __APP_VERSION__, - }); - // 把关闭java服务的的方法传给electron - window.electronApi?.setBaseURL?.(window._BaseURL); - // console.log(window.electronApi) - } - - // 初始化indexedDB - function initIndexedDB() { - indexedDB.createDB('chat2db', 1).then((db) => { - window._indexedDB = { - chat2db: db, - }; - }); - } - - // 监听系统(OS)主题变化 - function monitorOsTheme() { - function change(e: any) { - setAppTheme({ - ...appTheme, - backgroundColor: e.matches ? ThemeType.Dark : ThemeType.Light, - }); - } - - const themeMedia = window.matchMedia('(prefers-color-scheme: dark)'); - themeMedia.addListener(change); - return () => { - themeMedia.removeListener(change); - }; - } - - - - useEffect(() => { - detectionService(); - }, []); - - function detectionService() { - setServiceFail(false); - let flag = 0; - const time = setInterval(() => { - miscService - .testService() - .then(() => { - clearInterval(time); - setStartSchedule(2); - flag++; - }) - .catch(() => { - setStartSchedule(1); - flag++; - }); - if (flag > restartCount) { - setServiceFail(true); - clearInterval(time); - } - }, 1000); - } - - return ( -
- {initEnd && ( -
- {/* 服务启动中 */} - {startSchedule < 2 && ( -
- - {serviceFail && ( - <> -
- {i18n('common.text.contactUs')}: - - github - -
-
- {i18n('common.text.tryToRestart')} -
- - )} -
- )} - {/* 服务启动完成 */} - {startSchedule === 2 && } -
- )} - -
- ); -} diff --git a/chat2db-client/src/layouts/init/init.ts b/chat2db-client/src/layouts/init/init.ts index edf728efd..5d26280a5 100644 --- a/chat2db-client/src/layouts/init/init.ts +++ b/chat2db-client/src/layouts/init/init.ts @@ -27,4 +27,5 @@ const initLang = () => { document.cookie = `CHAT2DB.LOCALE=${lang};Expires=${date}`; } }; + export default init; diff --git a/chat2db-client/src/typings/theme.ts b/chat2db-client/src/typings/theme.ts index a4cb58080..53301bff1 100644 --- a/chat2db-client/src/typings/theme.ts +++ b/chat2db-client/src/typings/theme.ts @@ -1,6 +1,6 @@ import { ThemeType, PrimaryColorType } from '@/constants'; export interface ITheme { - backgroundColor: Exclude; + backgroundColor: ThemeType; primaryColor: PrimaryColorType; } From f6a96b8c5db6bc28a409e551786520d81e5c5182 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 13 Nov 2023 20:37:41 +0800 Subject: [PATCH 051/126] fix: fix bug --- chat2db-client/src/hooks/useTheme.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/chat2db-client/src/hooks/useTheme.ts b/chat2db-client/src/hooks/useTheme.ts index cea106e76..713024e09 100644 --- a/chat2db-client/src/hooks/useTheme.ts +++ b/chat2db-client/src/hooks/useTheme.ts @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react'; -import { addColorSchemeListener, colorSchemeListeners } from '@/layouts/GlobalLayout'; import { getOsTheme } from '@/utils'; import { ITheme } from '@/typings'; import { ThemeType, PrimaryColorType } from '@/constants'; From d90495cd09c4b950f68947419acef2e789a5c438 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 13 Nov 2023 20:45:25 +0800 Subject: [PATCH 052/126] chore: delete url param --- chat2db-client/src/pages/main/index.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 236a2e14c..4e832f04a 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -144,8 +144,7 @@ function MainPage(props: IProps) { } } - const urlTab = getUrlParam('tab'); - const initPage = urlTab || localStorage.getItem('curPage'); + const initPage = localStorage.getItem('curPage'); const initPageIndex = navConfig.findIndex((t) => `${t.key}` === initPage); const activeIndex = initPageIndex > -1 ? initPageIndex : 2; navConfig[activeIndex].isLoad = true; @@ -156,8 +155,6 @@ function MainPage(props: IProps) { if (item.openBrowser) { window.open(item.openBrowser, '_blank'); } else { - const newURL = updateQueryStringParameter('tab', item.key); - history.pushState({}, '', newURL); setActiveNav(item); } }; From 3a13c936b4d7fab137db854c3aec7f51d45a4328 Mon Sep 17 00:00:00 2001 From: tmlx1990 Date: Mon, 13 Nov 2023 23:21:28 +0800 Subject: [PATCH 053/126] =?UTF-8?q?=E5=A2=9E=E5=8A=A0DB2=E3=80=81DM?= =?UTF-8?q?=E3=80=81KingBase=E3=80=81PostgreSQL=E3=80=81Sqlite=E3=80=81Sql?= =?UTF-8?q?Server=E9=BB=98=E8=AE=A4=E5=80=BC=E4=B8=8B=E6=8B=89=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/chat2db/plugin/db2/DB2MetaData.java | 2 ++ .../plugin/db2/type/DB2DefaultValueEnum.java | 27 +++++++++++++++++++ .../java/ai/chat2db/plugin/dm/DMMetaData.java | 2 ++ .../plugin/dm/type/DMDefaultValueEnum.java | 27 +++++++++++++++++++ .../plugin/kingbase/KingBaseMetaData.java | 2 ++ .../type/KingBaseDefaultValueEnum.java | 27 +++++++++++++++++++ .../plugin/postgresql/PostgreSQLMetaData.java | 6 ++--- .../type/PostgreSQLDefaultValueEnum.java | 27 +++++++++++++++++++ .../chat2db/plugin/sqlite/SqliteMetaData.java | 2 ++ .../sqlite/type/SqliteDefaultValueEnum.java | 27 +++++++++++++++++++ .../plugin/sqlserver/SqlServerMetaData.java | 2 ++ .../type/SqlServerDefaultValueEnum.java | 27 +++++++++++++++++++ 12 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2DefaultValueEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMDefaultValueEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseDefaultValueEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLDefaultValueEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteDefaultValueEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerDefaultValueEnum.java diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java index f4770908f..90a0e2fa9 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2MetaData.java @@ -8,6 +8,7 @@ import ai.chat2db.plugin.db2.builder.DB2SqlBuilder; import ai.chat2db.plugin.db2.type.DB2ColumnTypeEnum; +import ai.chat2db.plugin.db2.type.DB2DefaultValueEnum; import ai.chat2db.plugin.db2.type.DB2IndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -152,6 +153,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .charsets(Lists.newArrayList()) .collations(Lists.newArrayList()) .indexTypes(DB2IndexTypeEnum.getIndexTypes()) + .defaultValues(DB2DefaultValueEnum.getDefaultValues()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2DefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2DefaultValueEnum.java new file mode 100644 index 000000000..01af0c14f --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/type/DB2DefaultValueEnum.java @@ -0,0 +1,27 @@ +package ai.chat2db.plugin.db2.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum DB2DefaultValueEnum { + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + DB2DefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(DB2DefaultValueEnum.values()).map(DB2DefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java index eafad8731..009a91506 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMMetaData.java @@ -8,6 +8,7 @@ import ai.chat2db.plugin.dm.builder.DMSqlBuilder; import ai.chat2db.plugin.dm.type.DMColumnTypeEnum; +import ai.chat2db.plugin.dm.type.DMDefaultValueEnum; import ai.chat2db.plugin.dm.type.DMIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -218,6 +219,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .charsets(Lists.newArrayList()) .collations(Lists.newArrayList()) .indexTypes(DMIndexTypeEnum.getIndexTypes()) + .defaultValues(DMDefaultValueEnum.getDefaultValues()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMDefaultValueEnum.java new file mode 100644 index 000000000..f85773a83 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/type/DMDefaultValueEnum.java @@ -0,0 +1,27 @@ +package ai.chat2db.plugin.dm.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum DMDefaultValueEnum { + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + DMDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(DMDefaultValueEnum.values()).map(DMDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java index 288e9add5..1e358de6e 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseMetaData.java @@ -2,6 +2,7 @@ import ai.chat2db.plugin.kingbase.builder.KingBaseSqlBuilder; import ai.chat2db.plugin.kingbase.type.KingBaseColumnTypeEnum; +import ai.chat2db.plugin.kingbase.type.KingBaseDefaultValueEnum; import ai.chat2db.plugin.kingbase.type.KingBaseIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -183,6 +184,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab //.charsets(PostgreSQLCharsetEnum.getCharsets()) //.collations(PostgreSQLCollationEnum.getCollations()) .indexTypes(KingBaseIndexTypeEnum.getIndexTypes()) + .defaultValues(KingBaseDefaultValueEnum.getDefaultValues()) .build(); } @Override diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseDefaultValueEnum.java new file mode 100644 index 000000000..cb6c58841 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/type/KingBaseDefaultValueEnum.java @@ -0,0 +1,27 @@ +package ai.chat2db.plugin.kingbase.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum KingBaseDefaultValueEnum { + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + KingBaseDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(KingBaseDefaultValueEnum.values()).map(KingBaseDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java index ef1413fa3..5ff2c6654 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLMetaData.java @@ -7,10 +7,7 @@ import java.util.stream.Collectors; import ai.chat2db.plugin.postgresql.builder.PostgreSQLSqlBuilder; -import ai.chat2db.plugin.postgresql.type.PostgreSQLCharsetEnum; -import ai.chat2db.plugin.postgresql.type.PostgreSQLCollationEnum; -import ai.chat2db.plugin.postgresql.type.PostgreSQLColumnTypeEnum; -import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum; +import ai.chat2db.plugin.postgresql.type.*; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -297,6 +294,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .charsets(PostgreSQLCharsetEnum.getCharsets()) .collations(PostgreSQLCollationEnum.getCollations()) .indexTypes(PostgreSQLIndexTypeEnum.getIndexTypes()) + .defaultValues(PostgreSQLDefaultValueEnum.getDefaultValues()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLDefaultValueEnum.java new file mode 100644 index 000000000..6d9728837 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLDefaultValueEnum.java @@ -0,0 +1,27 @@ +package ai.chat2db.plugin.postgresql.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum PostgreSQLDefaultValueEnum { + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + PostgreSQLDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(PostgreSQLDefaultValueEnum.values()).map(PostgreSQLDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java index 401137ae2..f8bc1213d 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteMetaData.java @@ -9,6 +9,7 @@ import ai.chat2db.plugin.sqlite.builder.SqliteBuilder; import ai.chat2db.plugin.sqlite.type.SqliteCollationEnum; import ai.chat2db.plugin.sqlite.type.SqliteColumnTypeEnum; +import ai.chat2db.plugin.sqlite.type.SqliteDefaultValueEnum; import ai.chat2db.plugin.sqlite.type.SqliteIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -56,6 +57,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .charsets(null) .collations(SqliteCollationEnum.getCollations()) .indexTypes(SqliteIndexTypeEnum.getIndexTypes()) + .defaultValues(SqliteDefaultValueEnum.getDefaultValues()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteDefaultValueEnum.java new file mode 100644 index 000000000..d713f29f1 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteDefaultValueEnum.java @@ -0,0 +1,27 @@ +package ai.chat2db.plugin.sqlite.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum SqliteDefaultValueEnum { + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + SqliteDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(SqliteDefaultValueEnum.values()).map(SqliteDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java index cc4b084bb..7b2711cd4 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java @@ -8,6 +8,7 @@ import ai.chat2db.plugin.sqlserver.builder.SqlServerSqlBuilder; import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; +import ai.chat2db.plugin.sqlserver.type.SqlServerDefaultValueEnum; import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -382,6 +383,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .charsets(null) .collations(null) .indexTypes(SqlServerIndexTypeEnum.getIndexTypes()) + .defaultValues(SqlServerDefaultValueEnum.getDefaultValues()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerDefaultValueEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerDefaultValueEnum.java new file mode 100644 index 000000000..1589a6723 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerDefaultValueEnum.java @@ -0,0 +1,27 @@ +package ai.chat2db.plugin.sqlserver.type; + +import ai.chat2db.spi.model.DefaultValue; + +import java.util.Arrays; +import java.util.List; + +public enum SqlServerDefaultValueEnum { + EMPTY_STRING("EMPTY_STRING"), + NULL("NULL"), + ; + private DefaultValue defaultValue; + + SqlServerDefaultValueEnum(String defaultValue) { + this.defaultValue = new DefaultValue(defaultValue); + } + + + public DefaultValue getDefaultValue() { + return defaultValue; + } + + public static List getDefaultValues() { + return Arrays.stream(SqlServerDefaultValueEnum.values()).map(SqlServerDefaultValueEnum::getDefaultValue).collect(java.util.stream.Collectors.toList()); + } + +} From 64b7e20be5bdf67fed961d9f9cae4665ea3f0397 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 14 Nov 2023 14:14:05 +0800 Subject: [PATCH 054/126] fix: fix bug --- chat2db-client/src/components/Console/index.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 4e122edd9..39473e713 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -406,6 +406,7 @@ function Console(props: IProps, ref: ForwardedRef) { const handleError = (error: any) => { console.error('Error:', error); setIsLoading(false); + setIsAiDrawerLoading(false); setIsStream(false); closeEventSource.current(); }; @@ -576,7 +577,21 @@ function Console(props: IProps, ref: ForwardedRef) { {/* */} - setIsAiDrawerOpen(false)}> + { + try { + setIsAiDrawerOpen(false); + setIsAiDrawerLoading(false); + setIsStream(false); + closeEventSource.current && closeEventSource.current(); + } catch (error) { + console.log('close drawer', error); + } + }} + >
{aiContent}
From 18cce246f2e693290242863d706dbaeb3c621891 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Tue, 14 Nov 2023 16:26:32 +0800 Subject: [PATCH 055/126] Fix DM index error --- .../plugin/mysql/builder/MysqlSqlBuilder.java | 2 +- .../ai/chat2db/spi/model/TableColumn.java | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java index abaf3346f..1c7258033 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java @@ -17,7 +17,7 @@ public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE "); if(StringUtils.isNotBlank(table.getDatabaseName())) { - script.append("`").append(table.getName()).append("`").append("."); + script.append("`").append(table.getDatabaseName()).append("`").append("."); } script.append("`").append(table.getName()).append("`").append(" (").append("\n"); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java index 797f27718..215f17344 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableColumn.java @@ -32,13 +32,13 @@ public class TableColumn { /** * 列名 */ - @JsonAlias({"COLUMN_NAME"}) + @JsonAlias({"COLUMN_NAME","column_name"}) private String name; /** * 表名 */ - @JsonAlias({"TABLE_NAME"}) + @JsonAlias({"TABLE_NAME","table_name"}) private String tableName; /** @@ -46,7 +46,7 @@ public class TableColumn { * 比如 varchar(100) ,double(10,6) */ - @JsonAlias({"TYPE_NAME"}) + @JsonAlias({"TYPE_NAME","type_name"}) private String columnType; /** @@ -54,7 +54,7 @@ public class TableColumn { * 比如 varchar ,double */ - @JsonAlias({"DATA_TYPE"}) + @JsonAlias({"DATA_TYPE","data_type"}) private Integer dataType; @@ -62,7 +62,7 @@ public class TableColumn { * 默认值 */ - @JsonAlias({"COLUMN_DEF"}) + @JsonAlias({"COLUMN_DEF","column_def"}) private String defaultValue; @@ -76,7 +76,7 @@ public class TableColumn { /** * 注释 */ - @JsonAlias({"REMARKS"}) + @JsonAlias({"REMARKS","remarks"}) private String comment; /** @@ -99,25 +99,25 @@ public class TableColumn { /** * 空间名 */ - @JsonAlias({"TABLE_SCHEM"}) + @JsonAlias({"TABLE_SCHEM","table_schem"}) private String schemaName; /** * 数据库名 */ - @JsonAlias({"TABLE_CAT"}) + @JsonAlias({"TABLE_CAT","table_cat"}) private String databaseName; - /** - * Data source dependent type name, for a UDT the type name is fully qualified - */ - private String typeName; +// /** +// * Data source dependent type name, for a UDT the type name is fully qualified +// */ +// private String typeName; /** * column size. */ - @JsonAlias({"COLUMN_SIZE"}) + @JsonAlias({"COLUMN_SIZE","column_size"}) private Integer columnSize; /** @@ -129,14 +129,14 @@ public class TableColumn { * the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable. */ - @JsonAlias({"DECIMAL_DIGITS"}) + @JsonAlias({"DECIMAL_DIGITS","decimal_digits"}) private Integer decimalDigits; /** * Radix (typically either 10 or 2) */ - @JsonAlias({"NUM_PREC_RADIX"}) + @JsonAlias({"NUM_PREC_RADIX","num_prec_radix"}) private Integer numPrecRadix; @@ -160,14 +160,14 @@ public class TableColumn { * index of column in table (starting at 1) */ - @JsonAlias({"ORDINAL_POSITION"}) + @JsonAlias({"ORDINAL_POSITION","ordinal_position"}) private Integer ordinalPosition; /** * ISO rules are used to determine the nullability for a column. */ - @JsonAlias({"NULLABLE"}) + @JsonAlias({"NULLABLE","nullable"}) private Integer nullable; /** @@ -204,11 +204,11 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TableColumn that = (TableColumn) o; - return Objects.equals(name, that.name) && Objects.equals(tableName, that.tableName) && Objects.equals(columnType, that.columnType) && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(autoIncrement, that.autoIncrement) && Objects.equals(comment, that.comment) && Objects.equals(typeName, that.typeName) && Objects.equals(columnSize, that.columnSize) && Objects.equals(decimalDigits, that.decimalDigits) && Objects.equals(numPrecRadix, that.numPrecRadix) && Objects.equals(sqlDataType, that.sqlDataType) && Objects.equals(ordinalPosition, that.ordinalPosition) && Objects.equals(nullable, that.nullable) && Objects.equals(extent, that.extent) && Objects.equals(charSetName, that.charSetName) && Objects.equals(collationName, that.collationName) && Objects.equals(value, that.value) && Objects.equals(unit, that.unit) && Objects.equals(sparse, that.sparse) && Objects.equals(defaultConstraintName, that.defaultConstraintName); + return Objects.equals(name, that.name) && Objects.equals(tableName, that.tableName) && Objects.equals(columnType, that.columnType) && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(autoIncrement, that.autoIncrement) && Objects.equals(comment, that.comment) && Objects.equals(columnSize, that.columnSize) && Objects.equals(decimalDigits, that.decimalDigits) && Objects.equals(numPrecRadix, that.numPrecRadix) && Objects.equals(sqlDataType, that.sqlDataType) && Objects.equals(ordinalPosition, that.ordinalPosition) && Objects.equals(nullable, that.nullable) && Objects.equals(extent, that.extent) && Objects.equals(charSetName, that.charSetName) && Objects.equals(collationName, that.collationName) && Objects.equals(value, that.value) && Objects.equals(unit, that.unit) && Objects.equals(sparse, that.sparse) && Objects.equals(defaultConstraintName, that.defaultConstraintName); } @Override public int hashCode() { - return Objects.hash(name, tableName, columnType, defaultValue, autoIncrement, comment, typeName, columnSize, decimalDigits, numPrecRadix, sqlDataType, ordinalPosition, nullable, extent, charSetName, collationName, value, unit, sparse, defaultConstraintName); + return Objects.hash(name, tableName, columnType, defaultValue, autoIncrement, comment, columnSize, decimalDigits, numPrecRadix, sqlDataType, ordinalPosition, nullable, extent, charSetName, collationName, value, unit, sparse, defaultConstraintName); } } From ef2e87edecd78ec3d7428b7e246ad2aebe3dd1a5 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 14 Nov 2023 16:54:46 +0800 Subject: [PATCH 056/126] feat: Modify Readme --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e5adcc369..69060e0a8 100644 --- a/README.md +++ b/README.md @@ -179,12 +179,18 @@ $ npm run build:web:prod / cp -r dist ../chat2db-server/chat2db-server-start/src ## ☎️ Contact Us -Please star and fork on GitHub before joining the group. -Follow our WeChat public account. +### WeChat - Click and join discord server +### Discord + +[![Discord](https://img.shields.io/badge/-Join%20us%20on%20Discord-%237289DA.svg?style=flat&logo=discord&logoColor=white)](https://discord.gg/N6JscF7q) + +## LICENSE + +The primary license used by this software is the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0), supplemented by the [Chat2DB License](./Chat2DB_LICENSE). + ## ❤️ Acknowledgements From 4ef1a8fde083584b44494c6d33436557ccd9f62f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 14 Nov 2023 17:04:39 +0800 Subject: [PATCH 057/126] refactor-workspace --- .vscode/settings.json | 8 + CHANGELOG.md | 5 + CHANGELOG_CN.md | 5 + .../src/blocks/SQLExecute/index.tsx | 88 +++--- .../src/blocks/Setting/About/index.less | 1 + .../blocks/Tree/TreeNodeRightClick/index.tsx | 2 +- .../src/components/CascaderDB/index.tsx | 1 - .../{ => components}/ChatInput/index.less | 0 .../{ => components}/ChatInput/index.tsx | 0 .../NewMonacoEditor/index.less | 0 .../NewMonacoEditor/index.tsx | 0 .../components/OperationLine/index.less | 37 +++ .../components/OperationLine/index.tsx | 62 ++++ .../components/SelectBoundInfo/index.tsx | 109 +++++++ .../ConsoleEditor/hooks/useModuleData.ts | 45 +++ .../ConsoleEditor/hooks/useSaveEditorData.ts | 126 ++++++++ .../src/components/ConsoleEditor/index.less | 37 --- .../src/components/ConsoleEditor/index.tsx | 254 ++++------------ .../src/components/EditDialog/index.tsx | 2 +- .../src/components/ExecuteSQL/index.tsx | 2 +- .../MonacoEditor/index.less | 0 .../MonacoEditor/index.tsx | 11 + .../MonacoEditor/monacoEditorConfig.ts | 21 +- .../MonacoEditor/syntax-parser/index.ts | 0 .../MonacoEditor/syntax-parser/lexer/index.ts | 0 .../MonacoEditor/syntax-parser/lexer/token.ts | 0 .../syntax-parser/parser/chain.ts | 0 .../syntax-parser/parser/define.ts | 0 .../syntax-parser/parser/index.ts | 0 .../syntax-parser/parser/match.ts | 0 .../syntax-parser/parser/scanner.ts | 0 .../syntax-parser/parser/utils.ts | 0 .../plugin/monaco-plugin/default-opts.ts | 0 .../plugin/monaco-plugin/index.ts | 0 .../plugin/monaco-plugin/parser.worker.ts | 0 .../plugin/sql-parser/base/define.ts | 0 .../plugin/sql-parser/base/four-operations.ts | 0 .../plugin/sql-parser/base/parser.ts | 0 .../plugin/sql-parser/base/reader.ts | 0 .../plugin/sql-parser/base/reserve-keys.ts | 0 .../plugin/sql-parser/base/utils.ts | 0 .../syntax-parser/plugin/sql-parser/index.tsx | 0 .../plugin/sql-parser/mysql/index.ts | 0 .../plugin/sql-parser/mysql/lexer.ts | 0 .../plugin/sql-parser/mysql/parser.ts | 0 .../MonacoEditor/useMonacoTheme.ts | 0 .../components/RightClickMenu/index.less | 8 +- .../components/RightClickMenu/index.tsx | 2 + .../components/TableBox/index.tsx | 2 +- .../components/NewWorkspaceRight/index.less | 1 + .../components/NewWorkspaceRight/index.tsx | 1 - .../components/WorkspaceRight/index.tsx | 284 +++++++++--------- .../components/WorkspaceTabs/index.less | 16 + .../components/WorkspaceTabs/index.tsx | 66 +++- .../src/pages/main/workspace/index.tsx | 2 +- chat2db-client/src/pages/test/index.tsx | 2 +- chat2db-client/src/service/connection.ts | 1 + chat2db-client/src/utils/index.ts | 4 +- 58 files changed, 738 insertions(+), 467 deletions(-) rename chat2db-client/src/components/ConsoleEditor/{ => components}/ChatInput/index.less (100%) rename chat2db-client/src/components/ConsoleEditor/{ => components}/ChatInput/index.tsx (100%) rename chat2db-client/src/components/ConsoleEditor/{ => components}/NewMonacoEditor/index.less (100%) rename chat2db-client/src/components/ConsoleEditor/{ => components}/NewMonacoEditor/index.tsx (100%) create mode 100644 chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less create mode 100644 chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx create mode 100644 chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx create mode 100644 chat2db-client/src/components/ConsoleEditor/hooks/useModuleData.ts create mode 100644 chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/index.less (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/index.tsx (97%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/monacoEditorConfig.ts (72%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/index.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/lexer/index.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/lexer/token.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/parser/chain.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/parser/define.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/parser/index.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/parser/match.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/parser/scanner.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/parser/utils.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts (100%) rename chat2db-client/src/components/{ConsoleEditor => }/MonacoEditor/useMonacoTheme.ts (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index b989422e5..0075f3fc0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "alicdn", "aliyuncs", "altool", + "andale", "antd", "asar", "AZUREAI", @@ -14,6 +15,7 @@ "charsets", "chmod", "CLOB", + "Consolas", "Datas", "datasource", "Datetime", @@ -36,8 +38,10 @@ "indexs", "jdbc", "kingbase", + "lucida", "macos", "Mddhhmmss", + "Menlo", "mkdir", "monaco", "msgtype", @@ -48,6 +52,7 @@ "ossutil", "partialize", "pgsql", + "plsql", "pnpm", "Popconfirm", "Prec", @@ -58,12 +63,15 @@ "samuelmeuli", "scroller", "Sercurity", + "singlestoredb", "sortablejs", "SQLSERVER", "temurin", "thymeleaf", "Tigger", "togglefullscreen", + "transactsql", + "trino", "umijs", "umirc", "USERANDPASSWORD", diff --git a/CHANGELOG.md b/CHANGELOG.md index 63e1e6b17..982872337 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ **更新日志** - 🐞【Fixed】Copy as insert first row lost problem +- 🐞【Fixed】DM database index bug +- 🐞【Fixed】Point Garbled code problem +- 🐞【Fixed】MariaDB connec database bug +- 🐞【Fixed】Issues 792 NullPointerException +- 🐞【Fixed】Kingbase8r6 error ## 3.0.11 diff --git a/CHANGELOG_CN.md b/CHANGELOG_CN.md index 4525f85d9..62aa5d69f 100644 --- a/CHANGELOG_CN.md +++ b/CHANGELOG_CN.md @@ -5,6 +5,11 @@ **更新日志** - 🐞【修复】复制为insert第一行丢失问题 +- 🐞【修复】达梦数据库index问题 +- 🐞【修复】Point 乱码问题 +- 🐞【修复】MariaDB连接数据库错误 +- 🐞【修复】#792 NullPointerException +- 🐞【修复】Kingbase8r6 错误 ## 3.0.11 diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 1f9c79a90..d4cb319eb 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -1,10 +1,10 @@ -import React, { memo, useRef, createContext } from 'react'; +import React, { memo, useRef, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import ConsoleEditor, { IConsoleRef } from '@/components/ConsoleEditor'; import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; -import { DatabaseTypeCode } from '@/constants'; +import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IProps { boundInfo: { @@ -12,80 +12,60 @@ interface IProps { dataSourceId: number; type: DatabaseTypeCode; schemaName?: string; + consoleId: number; + status: ConsoleStatus; }; initDDL: string; - consoleId: number; } interface IContext { - boundInfo: { + boundInfoContext: { databaseName: string; dataSourceId: number; type: DatabaseTypeCode; schemaName?: string; + consoleId: number; + status: ConsoleStatus; }; - consoleId: number; + setBoundInfoContext: (boundInfo: IContext['boundInfoContext']) => void; } -export const SQLExecuteContext = createContext({} as any); - const SQLExecute = memo((props) => { - const { boundInfo, initDDL } = props; + const { boundInfo: _boundInfo, initDDL } = props; const draggableRef = useRef(); const searchResultRef = useRef(null); const consoleRef = useRef(null); + const [boundInfo, setBoundInfo] = useState(_boundInfo) useUpdateEffect(() => { consoleRef.current?.editorRef?.setValue(initDDL, 'cover'); }, [initDDL]); return ( - -
- -
- { - searchResultRef.current?.handleExecuteSQL(sql); - }} - // isActive={isActive} - // onConsoleSave={() => { - // dispatch({ - // type: 'workspace/fetchGetSavedConsole', - // payload: { - // status: ConsoleStatus.RELEASE, - // orderByDesc: true, - // ...curWorkspaceParams, - // }, - // callback: (res: any) => { - // dispatch({ - // type: 'workspace/setConsoleList', - // payload: res.data, - // }); - // }, - // }); - // }} - // tables={curTableList || []} - // remainingUse={aiModel.remainingUse} - /> -
-
- -
-
-
-
+
+ +
+ { + searchResultRef.current?.handleExecuteSQL(sql); + }} + // isActive={isActive} + // tables={curTableList || []} + // remainingUse={aiModel.remainingUse} + /> +
+
+ +
+
+
); }); diff --git a/chat2db-client/src/blocks/Setting/About/index.less b/chat2db-client/src/blocks/Setting/About/index.less index 3f3a65236..8aab93b8d 100644 --- a/chat2db-client/src/blocks/Setting/About/index.less +++ b/chat2db-client/src/blocks/Setting/About/index.less @@ -8,6 +8,7 @@ } .brandLogo { margin-right: 15px; + flex-shrink: 0; } .currentVersion { font-size: 20px; diff --git a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx index 38b0d7b3a..e1b80e960 100644 --- a/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/blocks/Tree/TreeNodeRightClick/index.tsx @@ -7,7 +7,7 @@ import styles from './index.less'; // ----- components ----- import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; -import MonacoEditor, { IExportRefFunction } from '@/components/ConsoleEditor/MonacoEditor'; +import MonacoEditor, { IExportRefFunction } from '@/components/MonacoEditor'; import MenuLabel from '@/components/MenuLabel'; // ----- constants ----- diff --git a/chat2db-client/src/components/CascaderDB/index.tsx b/chat2db-client/src/components/CascaderDB/index.tsx index a4b344028..c8736e3a7 100644 --- a/chat2db-client/src/components/CascaderDB/index.tsx +++ b/chat2db-client/src/components/CascaderDB/index.tsx @@ -5,7 +5,6 @@ import cs from 'classnames'; import styles from './index.less'; import Iconfont from '../Iconfont'; import { databaseMap } from '@/constants/database'; -import { registerIntelliSenseDatabase } from '@/utils/IntelliSense'; interface IProps { className?: string; diff --git a/chat2db-client/src/components/ConsoleEditor/ChatInput/index.less b/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.less similarity index 100% rename from chat2db-client/src/components/ConsoleEditor/ChatInput/index.less rename to chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.less diff --git a/chat2db-client/src/components/ConsoleEditor/ChatInput/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.tsx similarity index 100% rename from chat2db-client/src/components/ConsoleEditor/ChatInput/index.tsx rename to chat2db-client/src/components/ConsoleEditor/components/ChatInput/index.tsx diff --git a/chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.less b/chat2db-client/src/components/ConsoleEditor/components/NewMonacoEditor/index.less similarity index 100% rename from chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.less rename to chat2db-client/src/components/ConsoleEditor/components/NewMonacoEditor/index.less diff --git a/chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/NewMonacoEditor/index.tsx similarity index 100% rename from chat2db-client/src/components/ConsoleEditor/NewMonacoEditor/index.tsx rename to chat2db-client/src/components/ConsoleEditor/components/NewMonacoEditor/index.tsx diff --git a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less new file mode 100644 index 000000000..68b527f18 --- /dev/null +++ b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less @@ -0,0 +1,37 @@ + +.consoleOptionsWrapper { + position: absolute; + bottom: 0px; + left: 0; + right: 0; + height: 40px; + display: flex; + align-items: center; + + display: flex; + margin: 0 12px; + justify-content: space-between; + align-items: center; +} + +.consoleOptionsLeft { + display: flex; + align-items: center; +} + +.runButton { + display: flex; + width: 70px; + align-items: center; + margin-right: 20px; + height: 28px; + + i { + margin-right: 4px; + } +} + +.saveButton { + width: 70px; + height: 28px; +} \ No newline at end of file diff --git a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx new file mode 100644 index 000000000..056c8d0b1 --- /dev/null +++ b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import i18n from '@/i18n'; +import { Button } from 'antd'; +import { IBoundInfo } from '../../index'; +import styles from './index.less'; +import Iconfont from '@/components/Iconfont'; +import SelectBoundInfo from '../SelectBoundInfo'; +import { formatSql } from '@/utils/sql'; + +interface IProps { + boundInfo: IBoundInfo; + saveConsole: (sql: string) => void; + executeSQL: () => void; + setBoundInfo: (boundInfo: IBoundInfo) => void; + editorRef: any; + hasSaveBtn: boolean; +} + +const OperationLine = (props: IProps) => { + const { boundInfo, saveConsole, editorRef, hasSaveBtn, executeSQL, setBoundInfo } = props; + + /** + * 格式化sql + */ + const handleSQLFormat = () => { + let setValueType = 'select'; + let sql = editorRef?.current?.getCurrentSelectContent(); + if (!sql) { + sql = editorRef?.current?.getAllContent() || ''; + setValueType = 'cover'; + } + formatSql(sql, boundInfo.type!).then((res) => { + editorRef?.current?.setValue(res, setValueType); + }); + }; + + return ( +
+
+ + {hasSaveBtn && ( + + )} + +
+ +
+ ); +}; + +export default OperationLine; diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx new file mode 100644 index 000000000..c6c256d70 --- /dev/null +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Select } from 'antd'; +import { IBoundInfo } from '../../index'; +import { useConnectionStore } from '@/store/connection'; +import connectionService from '@/service/connection'; + +interface IProps { + boundInfo: IBoundInfo; + setBoundInfo : (params: IBoundInfo) => void; +} + +interface IOption { + label: string; + value: any; +} + +const SelectBoundInfo = (props:IProps) => { + const { boundInfo, setBoundInfo } = props; + const connectionList = useConnectionStore((state) => state.connectionList); + const [databaseNameList, setDatabaseNameList] = useState(); + const [schemaList, setSchemaList] = useState(); + + const dataSourceList = useMemo(()=>{ + return connectionList?.map((item) => ({ + label: item.alias, + value: item.id, + })); + },[connectionList]) + + useEffect(() => { + if(boundInfo.dataSourceId === null || boundInfo.dataSourceId === undefined){ + return + } + connectionService.getDBList({ + dataSourceId: boundInfo.dataSourceId, + }).then((res) => { + const _databaseNameList = res.map((item) => ({ + label: item.name, + value: item.name, + })); + if(!_databaseNameList.length){ + getSchemaList() + } + setDatabaseNameList(_databaseNameList); + }); + }, [boundInfo.dataSourceId]); + + const getSchemaList = () => { + if(boundInfo.databaseName === null || boundInfo.databaseName === undefined){ + return + } + connectionService.getSchemaList({ + dataSourceId: boundInfo.dataSourceId, + databaseName: boundInfo?.databaseName + }).then((res: any) => { + setSchemaList(res.map((item) => ({ + label: item.name, + value: item.name, + }))); + }); + } + + useEffect(() => { + getSchemaList(); + }, [boundInfo.databaseName]); + + return ( +
+ { + setBoundInfo({ + ...boundInfo, + databaseName: value, + }) + }} + options={databaseNameList} + /> + } + { + boundInfo.databaseName && !!schemaList?.length && + - {noCommentDatabase.includes(curWorkspaceParams.databaseType) ? null : ( + {noCommentDatabase.includes(relyOnParams.databaseType) ? null : ( @@ -174,4 +180,11 @@ export default forwardRef((props: IProps, ref: ForwardedRef)
); -}); + + return { + createDatabaseDom, + openCreateDatabaseModal: openCreateDatabaseModal, + }; +}; + +export default useCreateDatabase; diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 56a9d989f..50de57f03 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -9,9 +9,9 @@ if (__ENV__ === 'local') { /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_n9w2jrk7fo.woff2?t=1700054654963') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_n9w2jrk7fo.woff?t=1700054654963') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_n9w2jrk7fo.ttf?t=1700054654963') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_5dpp20vk1k.woff2?t=1700137839323') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_5dpp20vk1k.woff?t=1700137839323') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_5dpp20vk1k.ttf?t=1700137839323') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/components/Loading/LoadingContent/index.less b/chat2db-client/src/components/Loading/LoadingContent/index.less index b69c62b43..ce5a11551 100644 --- a/chat2db-client/src/components/Loading/LoadingContent/index.less +++ b/chat2db-client/src/components/Loading/LoadingContent/index.less @@ -1,5 +1,4 @@ .loadingContent { - height: 100%; position: relative; } diff --git a/chat2db-client/src/components/Loading/LoadingContent/index.tsx b/chat2db-client/src/components/Loading/LoadingContent/index.tsx index 5342b106c..55af793d7 100644 --- a/chat2db-client/src/components/Loading/LoadingContent/index.tsx +++ b/chat2db-client/src/components/Loading/LoadingContent/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useImperativeHandle, ForwardedRef } from 'react'; +import React from 'react'; import styles from './index.less'; import classnames from 'classnames'; import StateIndicator from '@/components/StateIndicator'; @@ -11,7 +11,6 @@ interface IProps extends React.HTMLAttributes { handleEmpty?: boolean; isLoading?: boolean; coverLoading?: boolean; - ref: any; } export default function LoadingContent(props: IProps) { @@ -39,5 +38,5 @@ export default function LoadingContent(props: IProps) { ); }; - return
{renderContent()}
; + return
{renderContent()}
; } diff --git a/chat2db-client/src/constants/tree.ts b/chat2db-client/src/constants/tree.ts index 75c7dd180..7cbf09b42 100644 --- a/chat2db-client/src/constants/tree.ts +++ b/chat2db-client/src/constants/tree.ts @@ -42,4 +42,6 @@ export enum OperationColumn { OpenFunction = 'openFunction', // 打开函数 OpenProcedure = 'openProcedure', // 打开存储过程 OpenTrigger = 'openTrigger', // 打开触发器 + CreateSchema = 'createSchema', // 新建schema + CreateDatabase = 'createDatabase', // 新建database } diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 4c18366fc..411772f90 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -97,8 +97,6 @@ export default { 'common.text.noTableFoundDown': 'Switch databases at the top', 'common.title.preview': 'Preview', 'common.title.errorMessage': 'Error message', - 'common.Button.addDatabase': 'Add database', - 'common.Button.addSchema': 'Add schema', 'common.label.comment': 'Comment', 'common.label.name': 'Name', 'common.title.create': 'Create', diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 44fb6e610..171fe16d8 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -12,6 +12,8 @@ export default { 'workspace.menu.unPin': 'Unpin', 'workspace.menu.editTableData': 'Edit Table Data', 'workspace.menu.queryConsole': 'Query console', + 'workspace.menu.createDatabase': 'Create database', + 'workspace.menu.createSchema': 'Create schema', 'workspace.menu.deleteTablePlaceHolder': 'Please enter the name of the table you want to delete', 'workspace.tips.affirmDeleteTable': 'The table name you entered is not the same as the table name you want to delete, please confirm again', diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 7f55a9077..dbe77910e 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -96,8 +96,6 @@ export default { 'common.text.updateNow' : '立即更新', 'common.title.preview' : '预览', 'common.title.errorMessage': '错误信息', - 'common.Button.addDatabase': '添加数据库', - 'common.Button.addSchema': '添加Schema', 'common.label.comment': '备注', 'common.label.name': '名称', 'common.title.create': '创建', diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 92ed29abe..79b3844c0 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -12,6 +12,8 @@ export default { 'workspace.menu.unPin': '取消置顶', 'workspace.menu.editTableData': '编辑表数据', 'workspace.menu.queryConsole': '新建查询', + 'workspace.menu.createDatabase': '创建数据库', + 'workspace.menu.createSchema': '创建Schema', 'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名', 'workspace.tips.affirmDeleteTable': '输入的表名与要删除的表名不一致,请再次确认', 'workspace.table.total': '总数', diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index c511a5114..c4ceaebcd 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -134,6 +134,15 @@ const ConnectionsPage = () => { }); }; + const onSubmit = (data) => { + connectionService.save({ + ...data, + }).then((res) => { + getConnectionList(); + setConnectionActiveId(res); + }); + } + return ( <>
@@ -154,7 +163,7 @@ const ConnectionsPage = () => { )}
- +
diff --git a/chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx b/chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx index 0d94c5987..ba51ef2eb 100644 --- a/chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/NewTableList/index.tsx @@ -18,38 +18,11 @@ interface IProps { export default memo((props) => { const { className } = props; const [treeData, setTreeData] = useState(null); - const treeBoxRef = React.useRef(null); - const leftModuleTitleRef = React.useRef(null); const [searchValue, setSearchValue] = useState(''); - const { currentConnectionDetails } = useWorkspaceStore((state) => { - return { - currentConnectionDetails: state.currentConnectionDetails, - }; - }); - - // 监听treeBox滚动时,给leftModuleTitle添加下阴影 - useEffect(() => { - const treeBox = treeBoxRef.current; - const leftModuleTitleDom = leftModuleTitleRef.current; - if (!treeBox || !leftModuleTitleDom) { - return; - } - const handleScroll = () => { - const scrollTop = treeBox.scrollTop; - if (scrollTop > 0) { - leftModuleTitleDom.classList.add(styles.leftModuleTitleShadow); - } else { - leftModuleTitleDom.classList.remove(styles.leftModuleTitleShadow); - } - }; - treeBox.addEventListener('scroll', handleScroll); - return () => { - treeBox.removeEventListener('scroll', handleScroll); - }; - }, [treeBoxRef.current, leftModuleTitleRef.current]); + const currentConnectionDetails = useWorkspaceStore((state) => state.currentConnectionDetails); const getTreeData = (refresh = false) => { if (!currentConnectionDetails?.id) { @@ -78,10 +51,10 @@ export default memo((props) => { return (
-
- +
+
- +
); }); diff --git a/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx b/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx index ca20e1c7f..a4eaebc4f 100644 --- a/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx @@ -8,7 +8,6 @@ import { DatabaseTypeCode } from '@/constants'; // ----- components ----- import Iconfont from '@/components/Iconfont'; -import CreateDatabase, { ICreateDatabaseRef } from '@/components/CreateDatabase'; // ----- store ----- import { useWorkspaceStore } from '@/store/workspace'; @@ -25,39 +24,50 @@ const notSupportCreateDatabaseType = [DatabaseTypeCode.H2]; // 不支持创建schema的数据库类型 const notSupportCreateSchemaType = [DatabaseTypeCode.ORACLE]; -const operationLine = (props: IProps) => { +const OperationLine = (props: IProps) => { const [searchIng, setSearchIng] = useState(false); const { searchValue, setSearchValue, getTreeData } = props; - const createDatabaseRef = React.useRef(null); - const { currentConnectionDetails } = useWorkspaceStore((state) => { + const { currentConnectionDetails, openCreateDatabaseModal } = useWorkspaceStore((state) => { return { currentConnectionDetails: state.currentConnectionDetails, + openCreateDatabaseModal: state.openCreateDatabaseModal, }; }); + const handelOpenCreateDatabaseModal = () => { + openCreateDatabaseModal?.({ + type: 'database', + relyOnParams: { + databaseType: currentConnectionDetails!.type!, + dataSourceId: currentConnectionDetails!.id!, + }, + executedCallback: () => { + getTreeData(true); + } + }); + } + return ( <>
- {!notSupportCreateDatabaseType.includes(currentConnectionDetails?.type) && ( + {!notSupportCreateDatabaseType.includes(currentConnectionDetails!.type!) && ( { - createDatabaseRef.current?.setOpen(true, 'database'); - }} - code="" + onClick={handelOpenCreateDatabaseModal} + code="" box boxSize={20} - size={17} + size={15} /> )} { getTreeData(true); }} - code="" + code="" box boxSize={20} - size={13} + size={14} /> {searchIng ? ( { onClick={() => { setSearchIng(true); }} - code="" + code="" box boxSize={20} size={14} @@ -86,7 +96,7 @@ const operationLine = (props: IProps) => {
} + prefix={} value={searchValue} onChange={(e) => setSearchValue(e.target.value)} allowClear @@ -94,18 +104,8 @@ const operationLine = (props: IProps) => { />
)} - { - getTreeData(true); - }} - curWorkspaceParams={{ - dataSourceId: currentConnectionDetails?.id, - dataSourceName: currentConnectionDetails?.alias, - }} - ref={createDatabaseRef} - /> ); }; -export default memo(operationLine); +export default memo(OperationLine); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx index 40d169d1a..f8ff92c97 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -485,11 +485,6 @@ const WorkspaceHeader = memo((props) => {
- ); }); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx index fda2c4f14..a4274f59e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -1,18 +1,26 @@ -import React, { memo } from 'react'; +import React, { memo, useEffect } from 'react'; import classnames from 'classnames'; import styles from './index.less'; import NewTableList from '../NewTableList'; import WorkspaceLeftHeader from '../WorkspaceLeftHeader'; -import Iconfont from '@/components/Iconfont' - +import useCreateDatabase from '@/components/CreateDatabase'; +import { setOpenCreateDatabaseModal } from '@/store/workspace/modal'; const WorkspaceLeft = memo(() => { + const { createDatabaseDom, openCreateDatabaseModal } = useCreateDatabase(); + + useEffect(() => { + setOpenCreateDatabaseModal(openCreateDatabaseModal); + }, [openCreateDatabaseModal]); return ( -
- - -
+ <> +
+ + +
+ {createDatabaseDom} + ); }); diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index c2c91a8f5..4882ed95c 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -1,4 +1,4 @@ -import { IPageResponse, IConnectionDetails, IConnectionEnv, IPageParams } from '@/typings'; +import { IPageResponse, IConnectionDetails, ICreateConnectionDetails, IConnectionEnv, IPageParams } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import createRequest from './base'; @@ -32,7 +32,7 @@ const getList = createRequest>( const getDetails = createRequest<{ id: number }, IConnectionDetails>('/api/connection/datasource/:id', {}); -const save = createRequest('/api/connection/datasource/create', { +const save = createRequest('/api/connection/datasource/create', { method: 'post', delayTime: true, }); diff --git a/chat2db-client/src/store/workspace/index.ts b/chat2db-client/src/store/workspace/index.ts index 3e129e9b3..c828ef31c 100644 --- a/chat2db-client/src/store/workspace/index.ts +++ b/chat2db-client/src/store/workspace/index.ts @@ -4,8 +4,9 @@ import { devtools, persist } from 'zustand/middleware'; import { configStore, IConfigStore } from './config'; import { consoleStore, IConsoleStore } from './console'; import { commonStore, ICommonStore } from './common'; +import { modalStore , IModalStore } from './modal'; -export type IStore = IConfigStore & IConsoleStore & ICommonStore; +export type IStore = IConfigStore & IConsoleStore & ICommonStore & IModalStore; export const useWorkspaceStore: UseBoundStore> = create( devtools( @@ -14,6 +15,7 @@ export const useWorkspaceStore: UseBoundStore> = create( ...configStore(set), ...consoleStore(set), ...commonStore(set), + ...modalStore(), }), // persist config { @@ -26,5 +28,8 @@ export const useWorkspaceStore: UseBoundStore> = create( }), }, ), + { + name: "workspaceStore" + } ), ); diff --git a/chat2db-client/src/store/workspace/modal.ts b/chat2db-client/src/store/workspace/modal.ts new file mode 100644 index 000000000..eed1031aa --- /dev/null +++ b/chat2db-client/src/store/workspace/modal.ts @@ -0,0 +1,24 @@ +import { useWorkspaceStore } from './index'; +import { DatabaseTypeCode } from '@/constants'; +import { CreateType } from '@/components/CreateDatabase'; +export interface IModalStore { + openCreateDatabaseModal: ((params: { + type: CreateType; + relyOnParams: { + databaseType: DatabaseTypeCode; + dataSourceId: number; + databaseName?: string; + }; + executedCallback?: (status: true) => void; + }) => void) | null; +} + +const initModalStore: IModalStore = { + openCreateDatabaseModal: null, +}; + +export const modalStore = (): IModalStore => initModalStore; + +export const setOpenCreateDatabaseModal = (fn: any) => { + useWorkspaceStore.setState({ openCreateDatabaseModal: fn }); +}; From 3c356c6108e55e45660440e9cf956340a6d398d1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 16 Nov 2023 22:02:52 +0800 Subject: [PATCH 069/126] merge --- chat2db-client/src/blocks/Tree/index.less | 7 +- chat2db-client/src/blocks/Tree/index.tsx | 10 ++- .../src/components/CustomLayout/index.tsx | 5 +- .../components/TableBox/index.tsx | 8 +-- chat2db-client/src/hooks/useFocusData.ts | 4 +- chat2db-client/src/hooks/useGetConnection.ts | 12 +--- .../src/pages/main/connection/index.tsx | 6 +- .../components/WorkspaceLeftHeader/index.tsx | 4 +- chat2db-client/src/service/connection.ts | 6 +- .../src/store/common/copyFocusedContent.ts | 16 ++--- chat2db-client/src/store/common/index.ts | 13 ++-- chat2db-client/src/store/connection/index.ts | 71 ++++++++++--------- chat2db-client/src/store/console/index.ts | 11 ++- chat2db-client/src/store/monaco/index.ts | 11 ++- chat2db-client/src/store/workspace/common.ts | 16 +++-- chat2db-client/src/store/workspace/config.ts | 40 ++++++----- chat2db-client/src/store/workspace/index.ts | 21 +++--- chat2db-client/src/store/workspace/modal.ts | 4 +- chat2db-client/src/typings/connection.ts | 11 +++ .../chat2db/plugin/clickhouse/clickhouse.json | 2 + .../main/java/ai/chat2db/plugin/db2/db2.json | 2 + .../main/java/ai/chat2db/plugin/dm/dm.json | 2 + .../main/java/ai/chat2db/plugin/h2/h2.json | 2 + .../java/ai/chat2db/plugin/hive/hive.json | 2 + .../ai/chat2db/plugin/kingbase/kingbase.json | 2 + .../ai/chat2db/plugin/mariadb/mariadb.json | 2 + .../ai/chat2db/plugin/mongodb/mongodb.json | 2 + .../java/ai/chat2db/plugin/mysql/mysql.json | 2 + .../chat2db/plugin/oceanbase/oceanbase.json | 2 + .../java/ai/chat2db/plugin/oracle/oracle.json | 2 + .../java/ai/chat2db/plugin/postgresql/pg.json | 2 + .../java/ai/chat2db/plugin/presto/presto.json | 2 + .../java/ai/chat2db/plugin/redis/redis.json | 2 + .../java/ai/chat2db/plugin/sqlite/sqlite.json | 2 + .../chat2db/plugin/sqlserver/sqlserver.json | 2 + .../server/domain/api/model/DataSource.java | 5 ++ .../core/impl/DataSourceServiceImpl.java | 27 +++---- .../core/impl/JdbcDriverServiceImpl.java | 11 ++- .../src/main/resources/application-dev.yml | 2 +- .../data/source/vo/DataSourceVO.java | 10 +++ .../java/ai/chat2db/spi/config/DBConfig.java | 21 ++++++ .../ai/chat2db/spi/sql/Chat2DBContext.java | 4 ++ .../ai/chat2db/spi/sql/IDriverManager.java | 16 ++--- 43 files changed, 250 insertions(+), 154 deletions(-) diff --git a/chat2db-client/src/blocks/Tree/index.less b/chat2db-client/src/blocks/Tree/index.less index 231d3ae78..59b74c34a 100644 --- a/chat2db-client/src/blocks/Tree/index.less +++ b/chat2db-client/src/blocks/Tree/index.less @@ -1,13 +1,18 @@ @import '../../styles/var.less'; .treeBox { - padding: 0px 6px 10px; + padding: 2px 6px 10px; box-sizing: border-box; overflow: hidden; overflow-y: auto; height: 100%; } +// 如果treeBox滚动的高度>0那么久加一个上边框 +.treeBoxScroll { + border-top: 1px solid var(--color-border-secondary); +} + .leftModuleTitleShadow{ border-top: 1px solid red; } diff --git a/chat2db-client/src/blocks/Tree/index.tsx b/chat2db-client/src/blocks/Tree/index.tsx index 670be756f..22779e9f9 100644 --- a/chat2db-client/src/blocks/Tree/index.tsx +++ b/chat2db-client/src/blocks/Tree/index.tsx @@ -43,11 +43,19 @@ const Tree = (props: IProps) => { return ; }); }, [treeData]); + // 如果treeBox滚动的高度>0那么久加一个上边框 + const [treeBoxScrollTop, setTreeBoxScrollTop] = useState(0); + const handleScroll = (e: any) => { + setTreeBoxScrollTop(e.target.scrollTop); + }; return ( -
+
0 })} + onScroll={handleScroll} + > {treeNodes}
diff --git a/chat2db-client/src/components/CustomLayout/index.tsx b/chat2db-client/src/components/CustomLayout/index.tsx index 5803e4248..da5b63f48 100644 --- a/chat2db-client/src/components/CustomLayout/index.tsx +++ b/chat2db-client/src/components/CustomLayout/index.tsx @@ -2,6 +2,7 @@ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { useWorkspaceStore } from '@/store/workspace'; +import { togglePanelLeft, togglePanelRight } from '@/store/workspace/config'; interface IProps { className?: string; @@ -9,10 +10,8 @@ interface IProps { export default memo((props) => { const { className } = props; - const { panelLeft, panelRight, togglePanelLeft, togglePanelRight } = useWorkspaceStore((state) => { + const { panelLeft, panelRight} = useWorkspaceStore((state) => { return { - togglePanelLeft: state.togglePanelLeft, - togglePanelRight: state.togglePanelRight, panelLeft: state.layout.panelLeft, panelRight: state.layout.panelRight, }; diff --git a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx index 7edee5e60..32d8a0ccb 100644 --- a/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/components/TableBox/index.tsx @@ -25,7 +25,7 @@ import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; // store -import { useCommonStore } from '@/store/common'; +import { setFocusedContent } from '@/store/common/copyFocusedContent'; // 依赖组件 import ExecuteSQL from '@/components/ExecuteSQL'; @@ -145,12 +145,6 @@ export default function TableBox(props: ITableProps) { const [columnResize, setColumnResize] = useState([0]); // 表格的宽度 // const [tableBoxWidth, setTableBoxWidth] = useState(0); - // 判断是否聚焦在了可粘贴的区域中 hooks - const { setFocusedContent } = useCommonStore((state) => { - return { - setFocusedContent: state.setFocusedContent, - }; - }); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { diff --git a/chat2db-client/src/hooks/useFocusData.ts b/chat2db-client/src/hooks/useFocusData.ts index e063d7945..920f42bf4 100644 --- a/chat2db-client/src/hooks/useFocusData.ts +++ b/chat2db-client/src/hooks/useFocusData.ts @@ -1,12 +1,12 @@ import { useEffect } from 'react'; import { useCommonStore } from '@/store/common'; import { tableCopy, copy } from '@/utils' +import { setFocusedContent } from '@/store/common/copyFocusedContent'; // 如果用户点击的不是可复制的元素,就清空选中的内容 function useCopyFocusData() { - const { setFocusedContent, focusedContent } = useCommonStore((state) => { + const { focusedContent } = useCommonStore((state) => { return { - setFocusedContent: state.setFocusedContent, focusedContent: state.focusedContent } }); diff --git a/chat2db-client/src/hooks/useGetConnection.ts b/chat2db-client/src/hooks/useGetConnection.ts index f0f4adbd4..18edd3d15 100644 --- a/chat2db-client/src/hooks/useGetConnection.ts +++ b/chat2db-client/src/hooks/useGetConnection.ts @@ -1,21 +1,15 @@ import { useEffect } from 'react'; import connectionService from '@/service/connection'; -import { useConnectionStore } from '@/store/connection'; +import { setConnectionEnvList, getConnectionList } from '@/store/connection'; import { useWorkspaceStore } from '@/store/workspace'; +import { setCurrentConnectionDetails } from '@/store/workspace/common'; const useGetConnection = () => { - const { setConnectionEnvList, getConnectionList } = useConnectionStore((state) => { - return { - setConnectionEnvList: state.setConnectionEnvList, - getConnectionList: state.getConnectionList, - }; - }); - const { currentConnectionDetails, setCurrentConnectionDetails } = useWorkspaceStore((state) => { + const { currentConnectionDetails } = useWorkspaceStore((state) => { return { currentConnectionDetails: state.currentConnectionDetails, - setCurrentConnectionDetails: state.setCurrentConnectionDetails, }; }); diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index c4ceaebcd..1641f5ae0 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -22,15 +22,14 @@ import MenuLabel from '@/components/MenuLabel'; import useClickAndDoubleClick from '@/hooks/useClickAndDoubleClick'; // ----- store ----- -import { useConnectionStore } from '@/store/connection'; +import { useConnectionStore, getConnectionList } from '@/store/connection'; import styles from './index.less'; const ConnectionsPage = () => { - const { connectionList, getConnectionList } = useConnectionStore((state) => { + const { connectionList } = useConnectionStore((state) => { return { connectionList: state.connectionList, - getConnectionList: state.getConnectionList, }; }); const volatileRef = useRef(); @@ -101,7 +100,6 @@ const ConnectionsPage = () => { ] } - const renderConnectionMenuList = () => { return connectionList?.map((t) => { return ( diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx index 2f190b21c..8f207ee4e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx @@ -6,6 +6,7 @@ import styles from './index.less'; // ---- store ---- import { useConnectionStore } from '@/store/connection'; import { useWorkspaceStore } from '@/store/workspace'; +import { setCurrentConnectionDetails } from '@/store/workspace/common'; // ----- components ----- import Iconfont from '@/components/Iconfont'; @@ -25,10 +26,9 @@ export default memo((props) => { }; }); - const { currentConnectionDetails, setCurrentConnectionDetails } = useWorkspaceStore((state) => { + const { currentConnectionDetails } = useWorkspaceStore((state) => { return { currentConnectionDetails: state.currentConnectionDetails, - setCurrentConnectionDetails: state.setCurrentConnectionDetails, }; }); diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index 4882ed95c..6a781252e 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -1,4 +1,4 @@ -import { IPageResponse, IConnectionDetails, ICreateConnectionDetails, IConnectionEnv, IPageParams } from '@/typings'; +import { IPageResponse, IConnectionDetails, ICreateConnectionDetails, IConnectionEnv, IPageParams, IConnectionListItem } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import createRequest from './base'; @@ -25,7 +25,7 @@ interface IUploadDriver { /** * 查询连接列表 */ -const getList = createRequest>( +const getList = createRequest>( '/api/connection/datasource/list', {}, ); @@ -73,7 +73,7 @@ const downloadDriver = createRequest<{ dbType: string }, void>('/api/jdbc/driver method: 'get', }); -const saveDriver = createRequest('/api/jdbc/driver/save', { errorLevel: false, method: 'post' }); +const saveDriver = createRequest('/api/jdbc/driver/save', { method: 'post' }); const getEnvList = createRequest('/api/common/environment/list_all', { errorLevel: false }); diff --git a/chat2db-client/src/store/common/copyFocusedContent.ts b/chat2db-client/src/store/common/copyFocusedContent.ts index a71fe2927..05d96c069 100644 --- a/chat2db-client/src/store/common/copyFocusedContent.ts +++ b/chat2db-client/src/store/common/copyFocusedContent.ts @@ -1,14 +1,12 @@ +import {useCommonStore} from './index' export interface ICopyFocusedContent { focusedContent: any[][]| any[] | string | null; - setFocusedContent: (content: any[][] | any[] | string | null) => void; } -export const copyFocusedContent = (set): ICopyFocusedContent => ({ +export const initCopyFocusedContent = { focusedContent: null, - setFocusedContent: (focusedContent) => set((state) => { - return { - ...state, - focusedContent - } - }) -}); +} + +export const setFocusedContent: (content: any[][] | any[] | string | null) => void = (focusedContent) => { + return useCommonStore.setState({focusedContent}) +} diff --git a/chat2db-client/src/store/common/index.ts b/chat2db-client/src/store/common/index.ts index e54616f95..5b82d7371 100644 --- a/chat2db-client/src/store/common/index.ts +++ b/chat2db-client/src/store/common/index.ts @@ -1,14 +1,17 @@ -import { create, UseBoundStore, StoreApi } from 'zustand'; +import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { StoreApi } from 'zustand'; -import { copyFocusedContent, ICopyFocusedContent } from './copyFocusedContent'; +import { initCopyFocusedContent, ICopyFocusedContent } from './copyFocusedContent'; export type IStore = ICopyFocusedContent; -export const useCommonStore: UseBoundStore> = create( +export const useCommonStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( - (set) => ({ - ...copyFocusedContent(set), + () => ({ + ...initCopyFocusedContent, }), ), + shallow ); diff --git a/chat2db-client/src/store/connection/index.ts b/chat2db-client/src/store/connection/index.ts index 6c2498ceb..c0ce9e393 100644 --- a/chat2db-client/src/store/connection/index.ts +++ b/chat2db-client/src/store/connection/index.ts @@ -1,47 +1,48 @@ -/** - * 数据源的store - */ - -import { create, UseBoundStore, StoreApi } from 'zustand'; +import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { StoreApi } from 'zustand'; + import { IConnectionListItem, IConnectionEnv } from '@/typings/connection'; import connectionService from '@/service/connection'; export interface IConnectionStore { connectionList: IConnectionListItem[] | null; connectionEnvList: IConnectionEnv[] | null; - setConnectionList: (connectionList: IConnectionListItem[]) => void; - setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => void; - getConnectionList: () => Promise; } -export const connectionStore = (set): IConnectionStore => ({ +export const initConnectionStore = { connectionList: null, connectionEnvList: null, - setConnectionList: (connectionList: IConnectionListItem[]) => set({ connectionList }), - setConnectionEnvList: (connectionEnvList: IConnectionEnv[]) => set({ connectionEnvList }), - getConnectionList: () => { - return new Promise((resolve, reject) => { - connectionService - .getList({ - pageNo: 1, - pageSize: 1000, - refresh: true, - }) - .then((res) => { - const connectionList = res?.data || [] - set({ connectionList }); - resolve(connectionList); - }) - .catch(() => { - set({ connectionList: [] }); - reject([]); - }); - }); - }, -}); +}; -export const useConnectionStore: UseBoundStore> = create( - devtools((set) => ({ - ...connectionStore(set), - })), +export const useConnectionStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( + devtools(() => initConnectionStore), + shallow ); + +export const setConnectionList = (connectionList: IConnectionListItem[]) => { + return useConnectionStore.setState({ connectionList }); +}; + +export const setConnectionEnvList = (connectionEnvList: IConnectionEnv[]) => { + return useConnectionStore.setState({ connectionEnvList }); +}; +export const getConnectionList: () => Promise = () => { + return new Promise((resolve, reject) => { + connectionService + .getList({ + pageNo: 1, + pageSize: 1000, + refresh: true, + }) + .then((res) => { + const connectionList = res?.data || []; + useConnectionStore.setState({ connectionList }); + resolve(connectionList); + }) + .catch(() => { + useConnectionStore.setState({ connectionList: [] }); + reject([]); + }); + }); +}; diff --git a/chat2db-client/src/store/console/index.ts b/chat2db-client/src/store/console/index.ts index 23cb5dd53..963fac344 100644 --- a/chat2db-client/src/store/console/index.ts +++ b/chat2db-client/src/store/console/index.ts @@ -1,9 +1,7 @@ -/** - * 数据源的store - */ - -import { create, UseBoundStore, StoreApi } from 'zustand'; +import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { StoreApi } from 'zustand'; import { IConsole, ICreateConsoleParams } from '@/typings'; import { IWorkspaceTab } from '@/typings/workspace'; import historyService from '@/service/history'; @@ -21,8 +19,9 @@ const initConsoleStore = { workspaceTabList: null, } -export const useConsoleStore: UseBoundStore> = create( +export const useConsoleStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => (initConsoleStore)), + shallow ); export const getSavedConsoleList = () => { diff --git a/chat2db-client/src/store/monaco/index.ts b/chat2db-client/src/store/monaco/index.ts index c1480d4cd..119a5421c 100644 --- a/chat2db-client/src/store/monaco/index.ts +++ b/chat2db-client/src/store/monaco/index.ts @@ -1,9 +1,7 @@ -/** - * 数据源的store - */ - -import { create, UseBoundStore, StoreApi } from 'zustand'; +import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; import { devtools } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { StoreApi } from 'zustand'; export interface IMonacoStore { registerProvider: { @@ -20,7 +18,8 @@ const initMonacoStore = { registerProvider: null } -export const useMonacoStore: UseBoundStore> = create( +export const useMonacoStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => (initMonacoStore)), + shallow ); diff --git a/chat2db-client/src/store/workspace/common.ts b/chat2db-client/src/store/workspace/common.ts index 36011e009..95b4c3c33 100644 --- a/chat2db-client/src/store/workspace/common.ts +++ b/chat2db-client/src/store/workspace/common.ts @@ -1,12 +1,14 @@ -import { IConnectionDetails } from '@/typings/connection'; +import { IConnectionListItem } from '@/typings/connection'; +import { useWorkspaceStore } from './index' export interface ICommonStore { - currentConnectionDetails: Partial | null; - setCurrentConnectionDetails: (connectionDetails: Partial | null) => void; + currentConnectionDetails: IConnectionListItem | null; } -export const commonStore = (set): ICommonStore => ({ +export const initCommonStore: ICommonStore = { currentConnectionDetails: null, - setCurrentConnectionDetails: (connectionDetails: ICommonStore['currentConnectionDetails']) => - set({ currentConnectionDetails: connectionDetails }), -}); +} + +export const setCurrentConnectionDetails = (connectionDetails: ICommonStore['currentConnectionDetails']) => { + return useWorkspaceStore.setState({ currentConnectionDetails: connectionDetails }); +} diff --git a/chat2db-client/src/store/workspace/config.ts b/chat2db-client/src/store/workspace/config.ts index ea94828eb..b37e3cf70 100644 --- a/chat2db-client/src/store/workspace/config.ts +++ b/chat2db-client/src/store/workspace/config.ts @@ -1,32 +1,34 @@ +import {useWorkspaceStore} from './index' export interface IConfigStore { layout: { panelLeft: boolean; panelLeftWidth: number; panelRight: boolean; }; - togglePanelLeft: () => void; - togglePanelRight: () => void; } - -export const configStore = (set):IConfigStore => ({ +export const initConfigStore: IConfigStore = { layout: { panelLeft: true, panelRight: false, panelLeftWidth: 220, }, - togglePanelLeft: () => - set((state) => ({ - layout: { - ...state.layout, - panelLeft: !state.layout.panelLeft, - }, - })), - togglePanelRight: () => - set((state) => ({ - layout: { - ...state.layout, - panelRight: !state.layout.panelRight, - }, - })), -}) +} + +export const togglePanelRight = () => { + return useWorkspaceStore.setState((state) => ({ + layout: { + ...state.layout, + panelRight: !state.layout.panelRight, + }, + })) +} + +export const togglePanelLeft = () => { + return useWorkspaceStore.setState((state) => ({ + layout: { + ...state.layout, + panelLeft: !state.layout.panelLeft, + }, + })) +} diff --git a/chat2db-client/src/store/workspace/index.ts b/chat2db-client/src/store/workspace/index.ts index c828ef31c..ed98612b5 100644 --- a/chat2db-client/src/store/workspace/index.ts +++ b/chat2db-client/src/store/workspace/index.ts @@ -1,21 +1,23 @@ -import { create, UseBoundStore, StoreApi } from 'zustand'; -import { devtools, persist } from 'zustand/middleware'; +import { UseBoundStoreWithEqualityFn, createWithEqualityFn } from 'zustand/traditional'; +import { devtools,persist } from 'zustand/middleware'; +import { shallow } from 'zustand/shallow'; +import { StoreApi } from 'zustand'; -import { configStore, IConfigStore } from './config'; +import { initConfigStore, IConfigStore } from './config'; import { consoleStore, IConsoleStore } from './console'; -import { commonStore, ICommonStore } from './common'; -import { modalStore , IModalStore } from './modal'; +import { initCommonStore, ICommonStore } from './common'; +import { initModalStore , IModalStore } from './modal'; export type IStore = IConfigStore & IConsoleStore & ICommonStore & IModalStore; -export const useWorkspaceStore: UseBoundStore> = create( +export const useWorkspaceStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( persist( (set) => ({ - ...configStore(set), ...consoleStore(set), - ...commonStore(set), - ...modalStore(), + ...initConfigStore, + ...initCommonStore, + ...initModalStore, }), // persist config { @@ -32,4 +34,5 @@ export const useWorkspaceStore: UseBoundStore> = create( name: "workspaceStore" } ), + shallow ); diff --git a/chat2db-client/src/store/workspace/modal.ts b/chat2db-client/src/store/workspace/modal.ts index eed1031aa..c60b72a44 100644 --- a/chat2db-client/src/store/workspace/modal.ts +++ b/chat2db-client/src/store/workspace/modal.ts @@ -13,12 +13,10 @@ export interface IModalStore { }) => void) | null; } -const initModalStore: IModalStore = { +export const initModalStore: IModalStore = { openCreateDatabaseModal: null, }; -export const modalStore = (): IModalStore => initModalStore; - export const setOpenCreateDatabaseModal = (fn: any) => { useWorkspaceStore.setState({ openCreateDatabaseModal: fn }); }; diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index a9618b281..5b81fa75b 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -20,6 +20,8 @@ export interface IConnectionListItem { alias: string; environment: IConnectionEnv; type: DatabaseTypeCode; + supportDatabase: boolean; + supportSchema: boolean; } @@ -43,5 +45,14 @@ export interface IConnectionDetails { [key: string]: any; } +export interface IConnectionListItem { + id: number; + alias: string; + environment: IConnectionEnv; + type: DatabaseTypeCode; + supportDatabase: boolean; + supportSchema: boolean; +} + export type ICreateConnectionDetails = Omit diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json index f0528ea21..936c2c001 100644 --- a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json +++ b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/clickhouse.json @@ -1,5 +1,7 @@ { "dbType": "CLICKHOUSE", + "supportDatabase": false, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:clickhouse://localhost:8123/", diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json index 0f72b5964..b0b3d12a3 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/db2.json @@ -1,5 +1,7 @@ { "dbType": "DB2", + "supportDatabase": false, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:db2://localhost:50000/", diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json index e58e12548..075966e14 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/dm.json @@ -1,5 +1,7 @@ { "dbType": "DM", + "supportDatabase": false, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:dm://localhost:5236/", diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json index 552e0ef35..ee11410d5 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/h2.json @@ -1,5 +1,7 @@ { "dbType": "H2", + "supportDatabase": true, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:h2:tcp://localhost:9092/", diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json index 87f528c86..119cf9a71 100644 --- a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json +++ b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/hive.json @@ -1,5 +1,7 @@ { "dbType": "HIVE", + "supportDatabase": false, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:hive2://localhost:10000/", diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json index 540721bc6..c401d4785 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/kingbase.json @@ -1,5 +1,7 @@ { "dbType": "KINGBASE", + "supportDatabase": true, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:kingbase8://localhost:54321/", diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json index e2e33688d..59caa4a1a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/mariadb.json @@ -1,5 +1,7 @@ { "dbType": "MARIADB", + "supportDatabase": true, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:mariadb://localhost:3306/", diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json index cb4118f99..3a34cd3da 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/mongodb.json @@ -1,5 +1,7 @@ { "dbType": "MONGODB", + "supportDatabase": false, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:mongodb://localhost:27017", diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json index d326f1b28..5c720fa70 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/mysql.json @@ -1,5 +1,7 @@ { "dbType": "MYSQL", + "supportDatabase": true, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:mysql://localhost:3306/", diff --git a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json index 1e96bc9a6..c3e2cff36 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json +++ b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/oceanbase.json @@ -1,5 +1,7 @@ { "dbType": "OCEANBASE", + "supportDatabase": false, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:oceanbase://localhost:2883/", diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json index 18203f8e1..e2ae89fb6 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/oracle.json @@ -1,5 +1,7 @@ { "dbType": "ORACLE", + "supportDatabase": false, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:oracle:thin:@localhost:1521:XE", diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json index ea52c00e2..040611bbe 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/pg.json @@ -1,5 +1,7 @@ { "dbType": "POSTGRESQL", + "supportDatabase": true, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:postgresql://localhost:5432/postgres", diff --git a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json index e2b87b54f..b382dbf45 100644 --- a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json +++ b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/presto.json @@ -1,5 +1,7 @@ { "dbType": "PRESTO", + "supportDatabase": false, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:presto://localhost:8080/", diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json index 6c0be71d0..a10b33ae5 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/redis.json @@ -1,5 +1,7 @@ { "dbType": "REDIS", + "supportDatabase": false, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:redis://127.0.0.1:6379/0", diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json index a2acf8c2c..a12e7bd5e 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/sqlite.json @@ -1,5 +1,7 @@ { "dbType": "SQLITE", + "supportDatabase": false, + "supportSchema": false, "driverConfigList": [ { "url": "jdbc:sqlite:identifier.sqlite", diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json index ee8faea3c..1c54832ca 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/sqlserver.json @@ -1,5 +1,7 @@ { "dbType": "SQLSERVER", + "supportDatabase": true, + "supportSchema": true, "driverConfigList": [ { "url": "jdbc:sqlserver://localhost:1433;database=master", diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSource.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSource.java index 930aa7f8c..1abce5fa0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSource.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSource.java @@ -143,6 +143,11 @@ public class DataSource { */ private String serviceType; + + private boolean supportDatabase; + + private boolean supportSchema; + public LinkedHashMap getExtendMap() { if (ObjectUtils.isEmpty(extendInfo)) { return new LinkedHashMap<>(); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java index 8486450fe..26f69f813 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceServiceImpl.java @@ -35,6 +35,7 @@ import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.tools.common.util.EasySqlUtils; +import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.DataSourceConnect; import ai.chat2db.spi.model.Database; @@ -278,27 +279,27 @@ private void fillData(List list, DataSourceSelector selector) { fillEnvironment(list, selector); - fillExtendInfo(list); + fillSupportDatabase(list); } - private void fillExtendInfo(List list) { - for (DataSource dataSource : list) { - List keyValues = dataSource.getExtendInfo(); - if (CollectionUtils.isEmpty(keyValues)) { - continue; - } - for (KeyValue keyValue : keyValues) { - if (keyValue != null) { - if ("serviceName".equalsIgnoreCase(keyValue.getKey())) { + private void fillSupportDatabase(List list) { - } + if(CollectionUtils.isEmpty(list)) { + return; + } + for (DataSource dataSource:list) { + String type = dataSource.getType(); + if(StringUtils.isNotBlank(type)) { + DBConfig config = Chat2DBContext.getDBConfig(type); + if(config != null) { + dataSource.setSupportDatabase(config.isSupportDatabase()); + dataSource.setSupportSchema(config.isSupportSchema()); } } - } - } + private void fillEnvironment(List list, DataSourceSelector selector) { if (BooleanUtils.isNotTrue(selector.getEnvironment())) { return; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java index 7cb5f58ae..c1d6e8c79 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java @@ -9,6 +9,7 @@ import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.util.JdbcJarUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.google.common.collect.Lists; @@ -19,6 +20,7 @@ import java.io.File; import java.io.IOException; +import java.net.MalformedURLException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -58,7 +60,8 @@ public DataResult getDrivers(String dbType) { boolean flag = driverExists(driverConfig); if (flag && driverConfigMap.get(driverConfig.getJdbcDriver()) == null) { driverConfigMap.put(driverConfig.getJdbcDriver(), driverConfig); - setDriverDefaultProperty(driverConfig); + //TODO :临时解决方案,后续需要优化 + //setDriverDefaultProperty(driverConfig); } else { log.warn("Driver file not found: {}", driverConfig.getJdbcDriver()); } @@ -87,6 +90,12 @@ public ActionResult upload(String dbType, String jdbcDriverClass, String localPa driverDO.setJdbcDriverClass(jdbcDriverClass); driverDO.setDbType(dbType); driverDO.setJdbcDriver(localPath); + DriverConfig driverConfig = driverConfigConverter.do2Config(driverDO); + try { + IDriverManager.getClassLoader(driverConfig); + } catch (Exception e) { + throw new RuntimeException("Driver error,please check the driver file", e); + } jdbcDriverMapper.insert(driverDO); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application-dev.yml b/chat2db-server/chat2db-server-start/src/main/resources/application-dev.yml index 62dbd0eb0..f3eea546a 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application-dev.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application-dev.yml @@ -1,7 +1,7 @@ spring: datasource: # 配置自带数据库的相对路径 - url: jdbc:h2:~/.chat2db/db/chat2db_dev;MODE=MYSQL;FILE_LOCK=NO + url: jdbc:h2:~/.chat2db/db/chat2db_dev;FILE_LOCK=NO;MODE=MYSQL driver-class-name: org.h2.Driver h2: console: diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/vo/DataSourceVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/vo/DataSourceVO.java index a8c9531b2..f0a3c2017 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/vo/DataSourceVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/vo/DataSourceVO.java @@ -128,4 +128,14 @@ public class DataSourceVO { * 服务类型 */ private String serviceType; + + /** + * 是否支持数据库 + */ + private boolean supportDatabase; + + /** + * 是否支持schema + */ + private boolean supportSchema; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java index f2a2c4f3b..979ef078f 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/config/DBConfig.java @@ -42,6 +42,27 @@ public class DBConfig { private String simpleAlterTable; + private boolean supportDatabase; + + + private boolean supportSchema; + + public boolean isSupportDatabase() { + return supportDatabase; + } + + public void setSupportDatabase(boolean supportDatabase) { + this.supportDatabase = supportDatabase; + } + + public boolean isSupportSchema() { + return supportSchema; + } + + public void setSupportSchema(boolean supportSchema) { + this.supportSchema = supportSchema; + } + public String getDbType() { return dbType; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java index 69021a621..4ef3c1041 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java @@ -67,6 +67,10 @@ public static MetaData getMetaData(String dbType) { return PLUGIN_MAP.get(dbType).getMetaData(); } + public static DBConfig getDBConfig(String dbType) { + return PLUGIN_MAP.get(dbType).getDBConfig(); + } + public static DBConfig getDBConfig() { return PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBConfig(); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java index 383193e49..32173357a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/IDriverManager.java @@ -76,7 +76,7 @@ public static Connection getConnection(String url, String user, String password, } public static Connection getConnection(String url, Properties info, DriverConfig driver) - throws SQLException { + throws SQLException { if (Objects.isNull(url)) { throw new SQLException("The url cannot be null", SQL_STATE_CODE); } @@ -86,7 +86,7 @@ public static Connection getConnection(String url, Properties info, DriverConfig driverEntry = getJDBCDriver(driver); } Connection connection; - try { + try { connection = driverEntry.getDriver().connect(url, info); if (Objects.isNull(connection)) { throw new SQLException(String.format("driver.connect return null , No suitable driver found for url %s", url), SQL_STATE_CODE); @@ -98,7 +98,7 @@ public static Connection getConnection(String url, Properties info, DriverConfig if (Objects.isNull(con)) { throw new SQLException(String.format("Cannot create connection (%s)", sqlException.getMessage()), SQL_STATE_CODE, - sqlException); + sqlException); } return con; @@ -111,10 +111,10 @@ public static DriverPropertyInfo[] getProperty(DriverConfig driver) return null; } DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); - if (driverEntry == null) { - driverEntry = getJDBCDriver(driver); - } try { + if (driverEntry == null) { + driverEntry = getJDBCDriver(driver); + } String url = Objects.isNull(driver.getUrl()) ? "" : driver.getUrl(); return driverEntry.getDriver().getPropertyInfo(url, null); } catch (Exception var7) { @@ -153,7 +153,7 @@ private static DriverEntry getJDBCDriver(DriverConfig driver) } - public static ClassLoader getClassLoader(DriverConfig driverConfig) throws MalformedURLException { + public static ClassLoader getClassLoader(DriverConfig driverConfig) throws MalformedURLException, ClassNotFoundException { String jarPath = driverConfig.getJdbcDriver(); if (CLASS_LOADER_MAP.containsKey(jarPath)) { return CLASS_LOADER_MAP.get(jarPath); @@ -184,7 +184,7 @@ public static ClassLoader getClassLoader(DriverConfig driverConfig) throws Malfo } //urls[jarPaths.length] = new File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); - + cl.loadClass(driverConfig.getJdbcDriverClass()); } CLASS_LOADER_MAP.put(jarPath, cl); return cl; From 11ae6fc687944d52324f5cf07a97fb37e57c4d7c Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 17 Nov 2023 16:22:33 +0800 Subject: [PATCH 070/126] modal --- .../src/blocks/DatabaseTableEditor/index.tsx | 54 +++++++---- .../src/blocks/Tree/functions/pinTable.ts | 15 +++ .../src/blocks/Tree/functions/viewDDL.tsx | 16 +++ .../blocks/Tree/hooks/useGetRightClickMenu.ts | 97 ++++++++++++------- chat2db-client/src/blocks/Tree/index.tsx | 71 ++++++++------ chat2db-client/src/blocks/Tree/treeConfig.tsx | 4 +- .../src/components/CreateDatabase/index.tsx | 17 ++-- .../src/components/Modal/BaseModal/index.tsx | 41 ++++++++ chat2db-client/src/constants/tree.ts | 2 +- .../src/layouts/GlobalLayout/index.tsx | 4 +- .../src/layouts/init/GlobalComponent.tsx | 12 +++ .../components/OperationLine/index.tsx | 17 +++- .../components/WorkspaceLeft/index.tsx | 12 +-- .../components/WorkspaceTabs/index.tsx | 1 + chat2db-client/src/store/common/components.ts | 18 ++++ chat2db-client/src/store/common/index.ts | 4 +- chat2db-client/src/typings/tree.ts | 3 + 17 files changed, 281 insertions(+), 107 deletions(-) create mode 100644 chat2db-client/src/blocks/Tree/functions/pinTable.ts create mode 100644 chat2db-client/src/blocks/Tree/functions/viewDDL.tsx create mode 100644 chat2db-client/src/components/Modal/BaseModal/index.tsx create mode 100644 chat2db-client/src/layouts/init/GlobalComponent.tsx create mode 100644 chat2db-client/src/store/common/components.ts diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 4638b3949..7431a71b8 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -11,7 +11,7 @@ import sqlService, { IModifyTableSqlParams } from '@/service/sql'; import ExecuteSQL from '@/components/ExecuteSQL'; import { IEditTableInfo, IWorkspaceTab, IColumnTypes } from '@/typings'; import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; -import LoadingContent from '@/components/Loading/LoadingContent'; +import LoadingContent from '@/components/Loading/LoadingContent'; interface IProps { dataSourceId: number; databaseName: string; @@ -20,6 +20,7 @@ interface IProps { databaseType: DatabaseTypeCode; changeTabDetails: (data: IWorkspaceTab) => void; tabDetails: IWorkspaceTab; + submitCallback: () => void; } interface ITabItem { @@ -58,7 +59,16 @@ export interface IDatabaseSupportField { } export default memo((props: IProps) => { - const { databaseName, dataSourceId, tableName, schemaName, changeTabDetails, tabDetails, databaseType } = props; + const { + databaseName, + dataSourceId, + tableName, + schemaName, + changeTabDetails, + tabDetails, + databaseType, + submitCallback, + } = props; const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); const [viewSqlModal, setViewSqlModal] = useState(false); @@ -107,7 +117,7 @@ export default memo((props: IProps) => { getTableDetails(); } getDatabaseFieldTypeList(); - }, []) + }, []); // 获取数据库字段类型列表 const getDatabaseFieldTypeList = () => { @@ -151,12 +161,12 @@ export default memo((props: IProps) => { }) || []; const defaultValues = - res?.defaultValues?.map((i) => { - return { - value: i.defaultValue, - label: i.defaultValue, - }; - }) || []; + res?.defaultValues?.map((i) => { + return { + value: i.defaultValue, + label: i.defaultValue, + }; + }) || []; setDatabaseSupportField({ columnTypes, @@ -166,7 +176,7 @@ export default memo((props: IProps) => { defaultValues, }); }); - } + }; const getTableDetails = (myParams?: { tableNameProps?: string }) => { const { tableNameProps } = myParams || {}; @@ -180,16 +190,18 @@ export default memo((props: IProps) => { refresh: true, }; setIsLoading(true); - sqlService.getTableDetails(params).then((res) => { - const newTableDetails = lodash.cloneDeep(res); - setTableDetails(newTableDetails || {}); - setOldTableDetails(res); - }) - .finally(()=>{ - setIsLoading(false); - }) + sqlService + .getTableDetails(params) + .then((res) => { + const newTableDetails = lodash.cloneDeep(res); + setTableDetails(newTableDetails || {}); + setOldTableDetails(res); + }) + .finally(() => { + setIsLoading(false); + }); } - } + }; function submit() { if (baseInfoRef.current && columnListRef.current && indexListRef.current) { @@ -235,7 +247,9 @@ export default memo((props: IProps) => { }, }); } - } + // 保存成功后,刷新左侧树 + submitCallback(); + }; return ( { + const api = treeNodeData.pinned ? 'deleteTablePin' : 'addTablePin'; + mysqlService[api]({ + dataSourceId: treeNodeData.extraParams.dataSourceId, + databaseName: treeNodeData.extraParams.databaseName, + schemaName: treeNodeData.extraParams.schemaName, + tableName: treeNodeData.name, + }).then(()=>{ + loadData({ + refresh: true, + }) + }) +}; diff --git a/chat2db-client/src/blocks/Tree/functions/viewDDL.tsx b/chat2db-client/src/blocks/Tree/functions/viewDDL.tsx new file mode 100644 index 000000000..f5271bdc8 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/functions/viewDDL.tsx @@ -0,0 +1,16 @@ +// 置顶表格 +import React from 'react'; +import mysqlService from '@/service/sql'; + +import { openModal } from '@/store/common/components'; + +import MonacoEditor from '@/components/MonacoEditor'; + +export const viewDDL = (treeNodeData) => { + openModal({ + title: '查看DDL', + width: '60%', + footer: false, + content: , + }); +}; diff --git a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts index 2eb4c728a..d16808fa7 100644 --- a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts +++ b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts @@ -1,5 +1,5 @@ import { ITreeNode } from '@/typings'; -import { OperationColumn, WorkspaceTabType } from '@/constants'; +import { OperationColumn, WorkspaceTabType, TreeNodeType } from '@/constants'; import i18n from '@/i18n'; import { v4 as uuid } from 'uuid'; @@ -17,6 +17,8 @@ import { useWorkspaceStore } from '@/store/workspace'; // ---- functions ----- import { openView, openFunction, openProcedure, openTrigger } from '../functions/openAsyncSql'; +import { handelPinTable } from '../functions/pinTable'; +import { viewDDL } from '../functions/viewDDL'; // ----- utils ----- import { compatibleDataBaseName } from '@/utils/database'; @@ -30,31 +32,49 @@ interface IOperationColumnConfigItem { text: string; icon: string; doubleClickTrigger?: boolean; - handle: () => void; + handle: (treeNodeData: ITreeNode) => void; + discard?: boolean; +} + +interface IRightClickMenu { + key: number; + onClick: (treeNodeData: ITreeNode) => void; + type: OperationColumn; + doubleClickTrigger?: boolean; + labelProps: { + icon: string; + label: string; + }; } export const useGetRightClickMenu = (props: IProps) => { const { treeNodeData, loadData } = props; - const { openCreateDatabaseModal } = useWorkspaceStore((state) => { + const { openCreateDatabaseModal, currentConnectionDetails } = useWorkspaceStore((state) => { return { openCreateDatabaseModal: state.openCreateDatabaseModal, + currentConnectionDetails: state.currentConnectionDetails, }; }); - const handelOpenCreateDatabaseModal = (type: 'database' |'schema') => { + const handelOpenCreateDatabaseModal = (type: 'database' | 'schema') => { + + const relyOnParams = { + databaseType: treeNodeData.extraParams!.databaseType, + dataSourceId: treeNodeData.extraParams!.dataSourceId!, + databaseName: treeNodeData.name, + } + openCreateDatabaseModal?.({ type, - relyOnParams: { - databaseType: treeNodeData.extraParams!.databaseType, - dataSourceId: treeNodeData.extraParams!.dataSourceId!, - databaseName: treeNodeData?.name, - }, + relyOnParams, executedCallback: () => { - loadData(true); - } + loadData({ + refresh: true, + }); + }, }); - } + }; const rightClickMenu = useMemo(() => { // 拿出当前节点的配置 @@ -89,7 +109,7 @@ export const useGetRightClickMenu = (props: IProps) => { text: i18n('common.button.refresh'), icon: '\uec08', handle: () => { - loadData({ + treeNodeData.loadData?.({ refresh: true, }); }, @@ -124,9 +144,11 @@ export const useGetRightClickMenu = (props: IProps) => { databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, + submitCallback: () => {treeNodeData.loadData?.({refresh: true})}, }, }); }, + discard: (treeNodeData.treeNodeType === TreeNodeType.DATABASE && currentConnectionDetails?.supportSchema), }, // 删除表 @@ -134,7 +156,7 @@ export const useGetRightClickMenu = (props: IProps) => { text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', handle: () => { - // setVerifyDialog(true); + }, }, @@ -143,15 +165,17 @@ export const useGetRightClickMenu = (props: IProps) => { text: i18n('workspace.menu.ViewDDL'), icon: '\ue665', handle: () => { - // + viewDDL(treeNodeData) }, }, // 置顶 - [OperationColumn.Top]: { + [OperationColumn.Pin]: { text: treeNodeData.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: treeNodeData.pinned ? '\ue61d' : '\ue627', - handle: () => {}, + handle: () => { + handelPinTable({treeNodeData, loadData: treeNodeData.parentNode!.loadData!}); + }, }, // 编辑表 @@ -161,7 +185,7 @@ export const useGetRightClickMenu = (props: IProps) => { handle: () => { addWorkspaceTab({ id: `${OperationColumn.EditTable}-${treeNodeData.uuid}`, - title: i18n('editTable.button.createTable'), + title: treeNodeData?.name, type: WorkspaceTabType.EditTable, uniqueData: { dataSourceId: treeNodeData.extraParams!.dataSourceId!, @@ -169,6 +193,7 @@ export const useGetRightClickMenu = (props: IProps) => { databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, tableName: treeNodeData?.name, + submitCallback: () => {treeNodeData.parentNode?.loadData?.({refresh: true})}, }, }); }, @@ -189,10 +214,7 @@ export const useGetRightClickMenu = (props: IProps) => { icon: '\ue618', doubleClickTrigger: true, handle: () => { - const databaseName = compatibleDataBaseName( - treeNodeData.name!, - treeNodeData.extraParams!.databaseType, - ); + const databaseName = compatibleDataBaseName(treeNodeData.name!, treeNodeData.extraParams!.databaseType); addWorkspaceTab({ id: `${OperationColumn.OpenTable}-${treeNodeData.uuid}`, title: treeNodeData.name, @@ -202,7 +224,7 @@ export const useGetRightClickMenu = (props: IProps) => { databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, - sql: "select * from " + databaseName, + sql: 'select * from ' + databaseName, }, }); }, @@ -276,23 +298,28 @@ export const useGetRightClickMenu = (props: IProps) => { handle: () => { handelOpenCreateDatabaseModal('schema'); }, - } + discard: !currentConnectionDetails?.supportSchema, + }, }; // 根据配置生成右键菜单 - return excludeSomeOperation().map((t, i) => { + const finalList: IRightClickMenu[] = []; + excludeSomeOperation().forEach((t, i) => { const concrete = operationColumnConfig[t]; - return { - key: i, - onClick: concrete?.handle, - type: t, - doubleClickTrigger: concrete.doubleClickTrigger, - labelProps: { - icon: concrete?.icon, - label: concrete?.text, - }, - }; + if (!concrete.discard) { + finalList.push({ + key: i, + onClick: concrete?.handle, + type: t, + doubleClickTrigger: concrete.doubleClickTrigger, + labelProps: { + icon: concrete?.icon, + label: concrete?.text, + }, + }); + } }); + return finalList; }, [treeNodeData]); return rightClickMenu; diff --git a/chat2db-client/src/blocks/Tree/index.tsx b/chat2db-client/src/blocks/Tree/index.tsx index 22779e9f9..babbc3f22 100644 --- a/chat2db-client/src/blocks/Tree/index.tsx +++ b/chat2db-client/src/blocks/Tree/index.tsx @@ -67,31 +67,7 @@ const TreeNode = memo((props: TreeNodeIProps) => { const { data: initData, level, setShowParentNode: _setShowParentNode } = props; const [isLoading, setIsLoading] = useState(false); const indentArr = new Array(level).fill('indent'); - const [treeNodeData, setTreeNodeData] = useState({ - ...initData, - }); const { searchValue } = useContext(Context); - const isFocus = useTreeStore((state) => state.focusId) === treeNodeData.uuid; - - const showTreeNode = useMemo(() => { - const reg = new RegExp(searchValue || '', 'i'); - return reg.test(treeNodeData.name || ''); - }, [searchValue]); - - // 如果showTreeNode为true,那么他的父节点也要展示 - const [showParentNode, setShowParentNode] = useState(false); - - useEffect(() => { - if (showTreeNode) { - _setShowParentNode?.(true); - } - }, [showTreeNode]); - - useEffect(() => { - if (showParentNode) { - _setShowParentNode?.(true); - } - }, [showParentNode]); // 加载数据 function loadData(_props?: { refresh: boolean }) { @@ -137,6 +113,35 @@ const TreeNode = memo((props: TreeNodeIProps) => { }); } + // 当前节点数据 + const [treeNodeData, setTreeNodeData] = useState({ + ...initData, + loadData, + }); + + // 当前节点是否是focus + const isFocus = useTreeStore((state) => state.focusId) === treeNodeData.uuid; + + // 如果showTreeNode为true,那么他的父节点也要展示 + const [showParentNode, setShowParentNode] = useState(false); + + const showTreeNode = useMemo(() => { + const reg = new RegExp(searchValue || '', 'i'); + return reg.test(treeNodeData.name || ''); + }, [searchValue]); + + useEffect(() => { + if (showTreeNode) { + _setShowParentNode?.(true); + } + }, [showTreeNode]); + + useEffect(() => { + if (showParentNode) { + _setShowParentNode?.(true); + } + }, [showParentNode]); + //展开-收起 const handleClick = () => { if ( @@ -171,7 +176,7 @@ const TreeNode = memo((props: TreeNodeIProps) => { setFocusId(treeNodeData.uuid || ''); }; - // + // 双击节点 const handelDoubleClickTreeNode = () => { rightClickMenu.find((item) => item.doubleClickTrigger)?.onClick(); }; @@ -179,7 +184,17 @@ const TreeNode = memo((props: TreeNodeIProps) => { // 递归渲染 const treeNodes = useMemo(() => { return treeNodeData.children?.map((item: any, index: number) => { - return ; + return ( + + ); }); }, [treeNodeData]); @@ -192,7 +207,9 @@ const TreeNode = memo((props: TreeNodeIProps) => { const dropdownsItems: any = rightClickMenu.map((item) => { return { key: item.key, - onClick: item.onClick, + onClick: () => { + item.onClick(treeNodeData); + }, label: , }; }); diff --git a/chat2db-client/src/blocks/Tree/treeConfig.tsx b/chat2db-client/src/blocks/Tree/treeConfig.tsx index 4306a4c1c..651aed618 100644 --- a/chat2db-client/src/blocks/Tree/treeConfig.tsx +++ b/chat2db-client/src/blocks/Tree/treeConfig.tsx @@ -182,6 +182,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { operationColumn: [ OperationColumn.CreateConsole, OperationColumn.CreateSchema, + // OperationColumn.CreateTable, OperationColumn.CopyName, OperationColumn.Refresh, ], @@ -312,10 +313,11 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { operationColumn: [ OperationColumn.OpenTable, OperationColumn.CreateConsole, - OperationColumn.Top, + OperationColumn.Pin, OperationColumn.ViewDDL, OperationColumn.EditTable, OperationColumn.CopyName, + OperationColumn.Refresh, OperationColumn.DeleteTable, ], }, diff --git a/chat2db-client/src/components/CreateDatabase/index.tsx b/chat2db-client/src/components/CreateDatabase/index.tsx index 15f8d4d87..203fd9c8d 100644 --- a/chat2db-client/src/components/CreateDatabase/index.tsx +++ b/chat2db-client/src/components/CreateDatabase/index.tsx @@ -8,6 +8,7 @@ import sqlService from '@/service/sql'; import i18n from '@/i18n'; import { debounce } from 'lodash'; import { DatabaseTypeCode } from '@/constants'; +import { setOpenCreateDatabaseModal } from '@/store/workspace/modal'; interface IProps { relyOnParams: { @@ -29,7 +30,7 @@ export interface ICreateDatabase { // 创建database不支持注释的数据库 const noCommentDatabase = [DatabaseTypeCode.MYSQL]; -const useCreateDatabase = () => { +const CreateDatabase = () => { const [form] = Form.useForm(); const monacoEditorUuid = useMemo(() => uuid(), []); const monacoEditorRef = React.useRef(null); @@ -133,7 +134,11 @@ const useCreateDatabase = () => { executedCallbackRef.current = params.executedCallback; }; - const createDatabaseDom = !!relyOnParams && ( + useEffect(() => { + setOpenCreateDatabaseModal(openCreateDatabaseModal); + }, []); + + return (!!relyOnParams && ( { setOpen(false); @@ -179,12 +184,8 @@ const useCreateDatabase = () => { )}
- ); + )) - return { - createDatabaseDom, - openCreateDatabaseModal: openCreateDatabaseModal, - }; }; -export default useCreateDatabase; +export default CreateDatabase; diff --git a/chat2db-client/src/components/Modal/BaseModal/index.tsx b/chat2db-client/src/components/Modal/BaseModal/index.tsx new file mode 100644 index 000000000..b16027467 --- /dev/null +++ b/chat2db-client/src/components/Modal/BaseModal/index.tsx @@ -0,0 +1,41 @@ +import React, { memo, useEffect, useState } from 'react'; +import { Modal as AntdModal } from 'antd'; +import { injectOpenModal } from '@/store/common/components'; + +export type IModalData = { + title: string; + width?: string; + footer?: React.ReactNode | false; + content: React.ReactNode | false +} | null + +const Modal = memo(() => { + const [open, setOpen] = useState(false); + const [modalData, setModalData] = useState(null); + + const openModal = (params:IModalData)=>{ + setOpen(true) + setModalData(params) + } + + useEffect(() => { + injectOpenModal(openModal); + }, []); + + return ( + !!modalData && + { + setOpen(false); + }} + footer={modalData.footer || false} + > + {modalData.content} + + ); +}); + +export default Modal; diff --git a/chat2db-client/src/constants/tree.ts b/chat2db-client/src/constants/tree.ts index 7cbf09b42..2c6e1ee14 100644 --- a/chat2db-client/src/constants/tree.ts +++ b/chat2db-client/src/constants/tree.ts @@ -33,7 +33,7 @@ export enum OperationColumn { OpenTable = 'openTable', // 删除表 ViewDDL = 'viewDDL', // 查看ddl EditSource = 'editSource', // 编辑数据源 - Top = 'top', // 置顶 + Pin = 'pin', // 置顶 EditTable = 'editTable', // 编辑表 EditTableData = 'editTableData', // 编辑表数据 CopyName = 'copyName', // 复制名称 diff --git a/chat2db-client/src/layouts/GlobalLayout/index.tsx b/chat2db-client/src/layouts/GlobalLayout/index.tsx index 6d366db75..24add36c0 100644 --- a/chat2db-client/src/layouts/GlobalLayout/index.tsx +++ b/chat2db-client/src/layouts/GlobalLayout/index.tsx @@ -5,7 +5,6 @@ import { Button, ConfigProvider, Spin, Tooltip } from 'antd'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import service from '@/service/misc'; -import MyNotification from '@/components/MyNotification'; import useCopyFocusData from '@/hooks/useFocusData'; import { useTheme } from '@/hooks/useTheme'; import { getAntdThemeConfig } from '@/theme'; @@ -13,6 +12,7 @@ import { Outlet } from 'umi'; import init from '../init/init'; import { GithubOutlined, SyncOutlined, WechatOutlined } from '@ant-design/icons'; import { ThemeType } from '@/constants'; +import GlobalComponent from '../init/GlobalComponent'; import styles from './index.less'; const GlobalLayout = () => { @@ -81,7 +81,7 @@ const GlobalLayout = () => {
- + ); }; diff --git a/chat2db-client/src/layouts/init/GlobalComponent.tsx b/chat2db-client/src/layouts/init/GlobalComponent.tsx new file mode 100644 index 000000000..ad5563c30 --- /dev/null +++ b/chat2db-client/src/layouts/init/GlobalComponent.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import MyNotification from '@/components/MyNotification'; +import Modal from '@/components/Modal/BaseModal'; + +const GlobalComponent = () => { + return <> + + + +} + +export default GlobalComponent; diff --git a/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx b/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx index a4eaebc4f..58475234c 100644 --- a/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/OperationLine/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState } from 'react'; +import React, { memo, useMemo, useState } from 'react'; import i18n from '@/i18n'; import styles from './index.less'; import { Input } from 'antd'; @@ -35,8 +35,10 @@ const OperationLine = (props: IProps) => { }); const handelOpenCreateDatabaseModal = () => { + const type = currentConnectionDetails?.supportDatabase ? 'database' : 'schema'; + openCreateDatabaseModal?.({ - type: 'database', + type, relyOnParams: { databaseType: currentConnectionDetails!.type!, dataSourceId: currentConnectionDetails!.id!, @@ -47,11 +49,20 @@ const OperationLine = (props: IProps) => { }); } + const showCreate = useMemo(()=>{ + if(currentConnectionDetails?.supportDatabase){ + return !notSupportCreateDatabaseType.includes(currentConnectionDetails!.type!) + } + if(currentConnectionDetails?.supportSchema){ + return !notSupportCreateSchemaType.includes(currentConnectionDetails!.type!) + } + },[currentConnectionDetails]) + return ( <>
- {!notSupportCreateDatabaseType.includes(currentConnectionDetails!.type!) && ( + { showCreate && ( { - const { createDatabaseDom, openCreateDatabaseModal } = useCreateDatabase(); - - useEffect(() => { - setOpenCreateDatabaseModal(openCreateDatabaseModal); - }, [openCreateDatabaseModal]); return ( <> @@ -19,7 +13,7 @@ const WorkspaceLeft = memo(() => {
- {createDatabaseDom} + ); }); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx index 8e580f7a4..6d89966a5 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx @@ -162,6 +162,7 @@ const WorkspaceTabs = memo(() => { databaseType={uniqueData?.databaseType} schemaName={uniqueData?.schemaName} tableName={uniqueData.tableName} + submitCallback={uniqueData.submitCallback} /> ); }; diff --git a/chat2db-client/src/store/common/components.ts b/chat2db-client/src/store/common/components.ts new file mode 100644 index 000000000..2dc47cad8 --- /dev/null +++ b/chat2db-client/src/store/common/components.ts @@ -0,0 +1,18 @@ +import { useCommonStore } from './index'; +import { IModalData } from '@/components/Modal/BaseModal'; + +export interface IComponentsContent { + openModal: ((params: IModalData) => void) | null; +} + +export const initComponentsContent = { + openModal: null, +}; + +export const injectOpenModal = (openModal: IComponentsContent['openModal']) => { + return useCommonStore.setState({ openModal }); +}; + +export const openModal = (modal: IModalData) => { + return useCommonStore.getState().openModal?.(modal); +}; diff --git a/chat2db-client/src/store/common/index.ts b/chat2db-client/src/store/common/index.ts index 5b82d7371..dbae03adf 100644 --- a/chat2db-client/src/store/common/index.ts +++ b/chat2db-client/src/store/common/index.ts @@ -4,13 +4,15 @@ import { shallow } from 'zustand/shallow'; import { StoreApi } from 'zustand'; import { initCopyFocusedContent, ICopyFocusedContent } from './copyFocusedContent'; +import { initComponentsContent, IComponentsContent } from './components'; -export type IStore = ICopyFocusedContent; +export type IStore = ICopyFocusedContent & IComponentsContent; export const useCommonStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools( () => ({ ...initCopyFocusedContent, + ...initComponentsContent, }), ), shallow diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index cd6c361e1..4e13c10c0 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -26,6 +26,9 @@ export interface ITreeNode { extraParams?: IExtraParams; pinned?: boolean; // 是否置顶 comment?: string; // 表列的注释 + loadData?: (params:{refresh: boolean}) => void; // 加载数据的方法 + // 父元素 + parentNode?: ITreeNode; } // 视图 函数 触发器 过程 通用的返回结果 From 35843a77a5a9057931ea9dc510cc80bb43a02a8d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 17 Nov 2023 16:40:41 +0800 Subject: [PATCH 071/126] release_test2 --- .github/workflows/release_test2.yml | 205 ++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 .github/workflows/release_test2.yml diff --git a/.github/workflows/release_test2.yml b/.github/workflows/release_test2.yml new file mode 100644 index 000000000..a3efd5bf5 --- /dev/null +++ b/.github/workflows/release_test2.yml @@ -0,0 +1,205 @@ +name: Build Test Client + +on: + push: + branches: + - "release_test2" + +jobs: + release: + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + file_extension: ".exe" + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe" + build_arg: "--win --x64" + - os: macos-latest + arch: x86_64 + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg" + file_extension: ".dmg" + build_arg: "--mac --x64" + - os: macos-latest + arch: arm64 + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg" + file_extension: ".dmg" + build_arg: "--mac --arm64" + - os: ubuntu-latest + file_name: "https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage" + file_extension: ".AppImage" + build_arg: "--linux" + runs-on: ${{ matrix.os }} + + steps: + - name: Check out git repository + uses: actions/checkout@main + + # 安装JRE + - name: Install JRE + uses: actions/setup-java@main + with: + java-version: "17" + distribution: "temurin" + java-package: "jre" + # architecture: ${{ matrix.arch == 'arm64' && 'aarch64' || 'x64' }} + + # 开放TLS + - name: Enable TLS 1.0 and 1.1 in java.security + run: | + if [ "$RUNNER_OS" = "Windows" ]; then + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" + elif [ "$RUNNER_OS" = "Linux" ]; then + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" + elif [ "$RUNNER_OS" = "macOS" ]; then + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\(TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "$JAVA_HOME/conf/security/java.security" + fi + shell: bash + env: + RUNNER_OS: ${{ runner.os }} + JAVA_HOME: ${{ env.JAVA_HOME }} + + # JRE拷贝到前端静态目录 + - name: Copy JRE to static directory + run: | + mkdir -p chat2db-client/static + cp -r "$JAVA_HOME"/ chat2db-client/static/jre + if [ "${{ runner.os }}" != "Windows" ]; then + chmod -R 777 chat2db-client/static/jre + fi + shell: bash + env: + JAVA_HOME: ${{ env.JAVA_HOME }} + + # Linux中删除jre中相关文件 + - if: ${{ runner.os == 'Linux' }} + name: Delete File on Linux + run: | + cd chat2db-client/static/jre/ + ls -la + rm -rf legal + ls -la + + # 安装Node.js + - name: Install Node.js + uses: actions/setup-node@main + with: + node-version: "16" + cache: "yarn" + cache-dependency-path: chat2db-client/yarn.lock + + # 安装Java + - name: Install Java and Maven + uses: actions/setup-java@main + with: + java-version: "17" + distribution: "temurin" + cache: "maven" + + # 打包Web前端资源 + - name: Build FE Static + run: | + cd chat2db-client + yarn install --frozen-lockfile + yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 + cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front + cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ + + # 打包后端工程 & 发送到前端 + - name: Build BE Static + run: | + mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml + mkdir -p chat2db-client/versions/99.0.${{ github.run_id }}/static + echo -n 99.0.${{ github.run_id }} > chat2db-client/version + cp -r chat2db-client/version chat2db-client/versions/ + cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.0.${{ github.run_id }}/static/ + + # 打包桌面端前端资源 + - name: Prepare Build Electron + run: | + cd chat2db-client + yarn run build:web:desktop --app_version=99.0.${{ github.run_id }} --app_port=10822 + cp -r dist ./versions/99.0.${{ github.run_id }}/ + rm -r dist + + # 打包Electron + - name: Build/release Electron app + uses: samuelmeuli/action-electron-builder@v1 + with: + package_root: "chat2db-client/" + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} + mac_certs: ${{ secrets.mac_certs }} + mac_certs_password: ${{ secrets.mac_certs_password }} + skip_build: true + args: > + -c.appId=com.chat2db.test + -c.productName=Chat2DB-Test + -c.win.publisherName=Chat2DB-Test + -c.nsis.shortcutName=Chat2DB-Test + -c.extraMetadata.version=99.0.${{ github.run_id }}-Test + ${{ matrix.build_arg}} + + # 公证&签名 Mac App + - name: Notarize MacOS x86_64 App + if: matrix.os == 'macos-latest' && matrix.arch == 'x86_64' + run: | + xcrun notarytool store-credentials "Chat2DB" --apple-id "${{ secrets.MAC_APPLE_ID }}" --password "${{ secrets.MAC_APPLE_PASSWORD }}" --team-id "${{ secrets.MAC_TEAM_ID }}" + xcrun notarytool submit chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg --keychain-profile "Chat2DB" + + # Build Jar包 + - name: Prepare upload for Jar + if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} + run: | + mkdir -p oss_temp_file + cp chat2db-client/versions/99.0.${{ github.run_id }}/static/chat2db-server-start.jar ./oss_temp_file + cp -r chat2db-client/release/*.dmg ./oss_temp_file + cp -r chat2db-client/versions/99.0.${{ github.run_id }}/dist ./oss_temp_file/dist + cd chat2db-client/versions/99.0.${{ github.run_id }}/ && zip -r 99.0.${{ github.run_id }}.zip ./ + cp -r 99.0.${{ github.run_id }}.zip ../../../oss_temp_file + cd static/ && zip -r chat2db-server-start.zip ./ + cp -r chat2db-server-start.zip ../../../../oss_temp_file + + # 准备发往OSS的文件 + - name: Prepare upload for OSS + run: | + mkdir -p oss_temp_file + cp -r chat2db-client/release/*${{ matrix.file_extension }} ./oss_temp_file + + # 设置OSS + - name: Set up oss utils + uses: yizhoumo/setup-ossutil@v1 + with: + endpoint: "oss-accelerate.aliyuncs.com" + access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} + access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} + ossutil-version: "1.7.16" + + # 上传到OSS + - name: Upload to OSS + run: | + ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.0.${{ github.run_id }}/ + + # 发送到DingTalk + - name: Send dingtalk message + uses: ghostoy/dingtalk-action@master + with: + webhook: ${{ secrets.DINGTALK_WEBHOOK }} + msgtype: markdown + content: | + { + "title": "${{ matrix.os }}-test-打包完成通知", + "text": "# ${{ matrix.os }}-test-打包完成通知\n ![bang](https://oss.sqlgpt.cn/static/bang100.gif)\n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }})\n ### 下载地址:[${{matrix.file_name}}](${{matrix.file_name}})" + } + + # 发送Jar包地址到DingTalk + - if: ${{ runner.os == 'macOS' && matrix.arch == 'x86_64' }} + name: Send dingtalk message + uses: ghostoy/dingtalk-action@master + with: + webhook: ${{ secrets.DINGTALK_WEBHOOK }} + msgtype: markdown + content: | + { + "title": "Jar-test-构建完成通知", + "text": "### jar包下载地址:[https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip) " + } From 2abfe7df10d3b0094f7338aa29a3242ae8a1ce05 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 17 Nov 2023 16:43:01 +0800 Subject: [PATCH 072/126] release_test_2 --- .github/workflows/{release_test2.yml => release_test_2.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{release_test2.yml => release_test_2.yml} (99%) diff --git a/.github/workflows/release_test2.yml b/.github/workflows/release_test_2.yml similarity index 99% rename from .github/workflows/release_test2.yml rename to .github/workflows/release_test_2.yml index a3efd5bf5..743d26753 100644 --- a/.github/workflows/release_test2.yml +++ b/.github/workflows/release_test_2.yml @@ -3,7 +3,7 @@ name: Build Test Client on: push: branches: - - "release_test2" + - "release_test_2" jobs: release: From 2fc6d8e877e53e70da8a5548fdf4e6641737cb2a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 17 Nov 2023 21:26:28 +0800 Subject: [PATCH 073/126] feat:delete table --- .../blocks/Tree/functions/deleteTable.less | 21 ++++++ .../src/blocks/Tree/functions/deleteTable.tsx | 64 +++++++++++++++++++ .../src/blocks/Tree/functions/viewDDL.less | 6 ++ .../src/blocks/Tree/functions/viewDDL.tsx | 35 +++++++++- .../blocks/Tree/hooks/useGetRightClickMenu.ts | 3 +- .../src/components/Modal/BaseModal/index.tsx | 12 ++-- chat2db-client/src/i18n/en-us/workspace.ts | 2 + chat2db-client/src/i18n/zh-cn/workspace.ts | 3 + 8 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 chat2db-client/src/blocks/Tree/functions/deleteTable.less create mode 100644 chat2db-client/src/blocks/Tree/functions/deleteTable.tsx create mode 100644 chat2db-client/src/blocks/Tree/functions/viewDDL.less diff --git a/chat2db-client/src/blocks/Tree/functions/deleteTable.less b/chat2db-client/src/blocks/Tree/functions/deleteTable.less new file mode 100644 index 000000000..028684100 --- /dev/null +++ b/chat2db-client/src/blocks/Tree/functions/deleteTable.less @@ -0,0 +1,21 @@ +.deleteTableFooter{ + display: flex; + justify-content: center; + button{ + margin: 0px 15px; + } + +} + +.checkContainer{ + margin: 15px 0px 25px; +} + +.deleteModalContent{ + display: flex; + flex-direction: column; + justify-content: center; + font-size: 16px; + font-weight: 500; + text-align: center; +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/Tree/functions/deleteTable.tsx b/chat2db-client/src/blocks/Tree/functions/deleteTable.tsx new file mode 100644 index 000000000..d55d1507c --- /dev/null +++ b/chat2db-client/src/blocks/Tree/functions/deleteTable.tsx @@ -0,0 +1,64 @@ +// 置顶表格 +import React, { useState } from 'react'; +import mysqlService from '@/service/sql'; +import { Button, Checkbox } from 'antd'; +import { openModal } from '@/store/common/components'; +import styles from './deleteTable.less'; +import i18n from '@/i18n'; + +export const deleteTable = (treeNodeData) => { + openModal({ + width: '450px', + content: , + }); +}; + +export const DeleteModalContent = (params: { treeNodeData: any; openModal: any }) => { + const { treeNodeData } = params; + // 禁用确定按钮 + const [userChecked, setUserChecked] = useState(false); + + const onOk = () => { + const p: any = { + dataSourceId: treeNodeData.extraParams.dataSourceId, + databaseName: treeNodeData.extraParams.databaseName, + schemaName: treeNodeData.extraParams.schemaName, + tableName: treeNodeData.name, + }; + mysqlService.deleteTable(p).then(() => { + treeNodeData.parentNode.loadData({ + refresh: true, + }); + openModal(false); + }); + }; + + return ( +
+
{i18n('workspace.tree.delete.table.tip', `"${treeNodeData.name}"`)}
+
+ { + setUserChecked(e.target.checked); + }} + > + {i18n('workspace.tree.delete.tip')} + +
+
+ + +
+
+ ); +}; diff --git a/chat2db-client/src/blocks/Tree/functions/viewDDL.less b/chat2db-client/src/blocks/Tree/functions/viewDDL.less new file mode 100644 index 000000000..595b8ff8e --- /dev/null +++ b/chat2db-client/src/blocks/Tree/functions/viewDDL.less @@ -0,0 +1,6 @@ +.monacoEditorBox{ + border: 1px solid var(--color-border); + border-radius: 4px; + height: 60vh; + overflow: hidden; +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/Tree/functions/viewDDL.tsx b/chat2db-client/src/blocks/Tree/functions/viewDDL.tsx index f5271bdc8..21bedebac 100644 --- a/chat2db-client/src/blocks/Tree/functions/viewDDL.tsx +++ b/chat2db-client/src/blocks/Tree/functions/viewDDL.tsx @@ -1,16 +1,47 @@ // 置顶表格 import React from 'react'; import mysqlService from '@/service/sql'; +import { v4 as uuid } from 'uuid'; +import styles from './viewDDL.less'; import { openModal } from '@/store/common/components'; import MonacoEditor from '@/components/MonacoEditor'; export const viewDDL = (treeNodeData) => { + const getSql = () => { + return new Promise((resolve) => { + mysqlService + .exportCreateTableSql({ + dataSourceId: treeNodeData.extraParams.dataSourceId, + databaseName: treeNodeData.extraParams.databaseName, + schemaName: treeNodeData.extraParams.schemaName, + tableName: treeNodeData.name, + }) + .then((res) => { + resolve(res); + }); + }); + }; + openModal({ - title: '查看DDL', + title: `DDL-${treeNodeData.name}`, width: '60%', + height: '60%', footer: false, - content: , + content: ( +
+ +
+ ), + }); +}; + +export const MonacoEditorAsync = (params: { getSql: any }) => { + const { getSql } = params; + const monacoEditorRef = React.useRef(); + getSql().then((sql) => { + monacoEditorRef.current.setValue(sql); }); + return ; }; diff --git a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts index d16808fa7..84cee72c4 100644 --- a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts +++ b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts @@ -19,6 +19,7 @@ import { useWorkspaceStore } from '@/store/workspace'; import { openView, openFunction, openProcedure, openTrigger } from '../functions/openAsyncSql'; import { handelPinTable } from '../functions/pinTable'; import { viewDDL } from '../functions/viewDDL'; +import { deleteTable } from '../functions/deleteTable'; // ----- utils ----- import { compatibleDataBaseName } from '@/utils/database'; @@ -156,7 +157,7 @@ export const useGetRightClickMenu = (props: IProps) => { text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', handle: () => { - + deleteTable(treeNodeData); }, }, diff --git a/chat2db-client/src/components/Modal/BaseModal/index.tsx b/chat2db-client/src/components/Modal/BaseModal/index.tsx index b16027467..78d954def 100644 --- a/chat2db-client/src/components/Modal/BaseModal/index.tsx +++ b/chat2db-client/src/components/Modal/BaseModal/index.tsx @@ -3,19 +3,23 @@ import { Modal as AntdModal } from 'antd'; import { injectOpenModal } from '@/store/common/components'; export type IModalData = { - title: string; + title?: string; width?: string; footer?: React.ReactNode | false; content: React.ReactNode | false -} | null +} | null | false const Modal = memo(() => { const [open, setOpen] = useState(false); const [modalData, setModalData] = useState(null); const openModal = (params:IModalData)=>{ - setOpen(true) - setModalData(params) + if(params === false){ + setOpen(false) + }else{ + setOpen(true) + setModalData(params) + } } useEffect(() => { diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 171fe16d8..e7d079466 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -28,4 +28,6 @@ export default { 'workspace.tree.function': 'Function', 'workspace.tree.procedure': 'Procedure', 'workspace.tree.search.placeholder': 'Search in the expand node', + 'workspace.tree.delete.tip': 'I understand that this operation is permanently deleted', + 'workspace.tree.delete.table.tip': 'Are you sure you want to delete the table {1}?', }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 79b3844c0..3518b2de3 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -27,4 +27,7 @@ export default { 'workspace.tree.function': '函数', 'workspace.tree.procedure': '存储过程', 'workspace.tree.search.placeholder': '在展开节点中搜索', + 'workspace.tree.delete.tip': '我了解该操作是永久性删除', + 'workspace.tree.delete.table.tip': '确定要删除表{1}吗?', + }; From 7fae7b7725f6f28ec7131531427bf346f63df7de Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 17 Nov 2023 21:28:26 +0800 Subject: [PATCH 074/126] modal destroyOnClose --- chat2db-client/src/components/Modal/BaseModal/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/src/components/Modal/BaseModal/index.tsx b/chat2db-client/src/components/Modal/BaseModal/index.tsx index 78d954def..90593032a 100644 --- a/chat2db-client/src/components/Modal/BaseModal/index.tsx +++ b/chat2db-client/src/components/Modal/BaseModal/index.tsx @@ -35,6 +35,7 @@ const Modal = memo(() => { onCancel={() => { setOpen(false); }} + destroyOnClose={true} footer={modalData.footer || false} > {modalData.content} From ed1a3268ecd67fb459055548fa3119abbf294dab Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 19 Nov 2023 16:09:49 +0800 Subject: [PATCH 075/126] iconfont --- chat2db-client/src/assets/font/demo.css | 539 ++ .../src/assets/font/demo_index.html | 4650 +++++++++++++++++ chat2db-client/src/assets/font/iconfont.css | 58 +- chat2db-client/src/assets/font/iconfont.js | 2 +- chat2db-client/src/assets/font/iconfont.json | 91 + chat2db-client/src/assets/font/iconfont.ttf | Bin 59112 -> 63020 bytes chat2db-client/src/assets/font/iconfont.woff | Bin 36844 -> 39396 bytes chat2db-client/src/assets/font/iconfont.woff2 | Bin 31436 -> 33652 bytes .../components/OperationLine/index.less | 4 + .../components/OperationLine/index.tsx | 10 +- .../components/SelectBoundInfo/index.less | 3 + .../components/SelectBoundInfo/index.tsx | 104 +- .../src/components/ConsoleEditor/index.tsx | 6 +- .../MonacoEditor/monacoEditorConfig.ts | 4 + chat2db-client/src/hooks/useCreateConsole.ts | 4 + .../components/OperationLine/index.tsx | 2 +- .../components/WorkspaceTabs/index.tsx | 3 +- chat2db-client/src/typings/console.ts | 6 +- 18 files changed, 5433 insertions(+), 53 deletions(-) create mode 100644 chat2db-client/src/assets/font/demo.css create mode 100644 chat2db-client/src/assets/font/demo_index.html create mode 100644 chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.less diff --git a/chat2db-client/src/assets/font/demo.css b/chat2db-client/src/assets/font/demo.css new file mode 100644 index 000000000..a67054a0a --- /dev/null +++ b/chat2db-client/src/assets/font/demo.css @@ -0,0 +1,539 @@ +/* Logo 字体 */ +@font-face { + font-family: "iconfont logo"; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); +} + +.logo { + font-family: "iconfont logo"; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: font-size 0.25s linear, width 0.25s linear; + -moz-transition: font-size 0.25s linear, width 0.25s linear; + transition: font-size 0.25s linear, width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown>p, +.markdown>blockquote, +.markdown>.highlight, +.markdown>ol, +.markdown>ul { + width: 80%; +} + +.markdown ul>li { + list-style: circle; +} + +.markdown>ul li, +.markdown blockquote ul>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown>ul li p, +.markdown>ol li p { + margin: 0.6em 0; +} + +.markdown ol>li { + list-style: decimal; +} + +.markdown>ol li, +.markdown blockquote ol>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown>table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown>table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown>table th, +.markdown>table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown>table th { + background: #F7F7F7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown>br, +.markdown>p>br { + clear: both; +} + + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, +pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, +code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre)>code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre)>code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/chat2db-client/src/assets/font/demo_index.html b/chat2db-client/src/assets/font/demo_index.html new file mode 100644 index 000000000..5b0025b30 --- /dev/null +++ b/chat2db-client/src/assets/font/demo_index.html @@ -0,0 +1,4650 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
+

+ + +

+ +
+
+
    + +
  • + +
    database
    +
    &#xe669;
    +
  • + +
  • + +
    筛选
    +
    &#xe888;
    +
  • + +
  • + +
    刷新
    +
    &#xe668;
    +
  • + +
  • + +
    加号_o
    +
    &#xeb78;
    +
  • + +
  • + +
    数据库_jurassic
    +
    &#xe6a9;
    +
  • + +
  • + +
    权限
    +
    &#xe667;
    +
  • + +
  • + +
    sharpicons_add-database
    +
    &#xe816;
    +
  • + +
  • + +
    组织管理
    +
    &#xe663;
    +
  • + +
  • + +
    空间
    +
    &#xe691;
    +
  • + +
  • + +
    电话_填充
    +
    &#xe678;
    +
  • + +
  • + +
    邮箱
    +
    &#xe672;
    +
  • + +
  • + +
    微信
    +
    &#xe65d;
    +
  • + +
  • + +
    谷歌
    +
    &#xeaa8;
    +
  • + +
  • + 𐂾 +
    下箭头-copy
    +
    &#x100be;
    +
  • + +
  • + +
    查看
    +
    &#xe788;
    +
  • + +
  • + +
    clone
    +
    &#xe8db;
    +
  • + +
  • + +
    提交
    +
    &#xe687;
    +
  • + +
  • + +
    查看
    +
    &#xe665;
    +
  • + +
  • + +
    复制
    +
    &#xec7a;
    +
  • + +
  • + +
    icon_answer
    +
    &#xe686;
    +
  • + +
  • + +
    icon_question
    +
    &#xe6a8;
    +
  • + +
  • + 𐂽 +
    发送
    +
    &#x100bd;
    +
  • + +
  • + +
    重启
    +
    &#xe662;
    +
  • + +
  • + +
    提醒
    +
    &#xe6cc;
    +
  • + +
  • + +
    提醒
    +
    &#xe661;
    +
  • + +
  • + +
    提醒
    +
    &#xe716;
    +
  • + +
  • + +
    升级
    +
    &#xe69c;
    +
  • + +
  • + +
    全局_升级
    +
    &#xe659;
    +
  • + +
  • + +
    关于我们
    +
    &#xe65c;
    +
  • + +
  • + +
    ico版本更新
    +
    &#xe67d;
    +
  • + +
  • + +
    对话气泡
    +
    &#xe657;
    +
  • + +
  • + +
    角色权限
    +
    &#xe658;
    +
  • + +
  • + +
    preview
    +
    &#xe654;
    +
  • + +
  • + +
    导入
    +
    &#xe653;
    +
  • + +
  • + +
    终止
    +
    &#xe652;
    +
  • + +
  • + +
    退出
    +
    &#xe6b2;
    +
  • + +
  • + +
    控桩终端
    +
    &#xe6bb;
    +
  • + +
  • + +
    撤销
    +
    &#xe6e2;
    +
  • + +
  • + +
    向上
    +
    &#xe650;
    +
  • + +
  • + +
    查看
    +
    &#xe651;
    +
  • + +
  • + +
    编辑数据_编辑录入操作_jurassic
    +
    &#xe6f2;
    +
  • + +
  • + +
    编辑表格_编辑录入操作_jurassic
    +
    &#xe6f3;
    +
  • + +
  • + +
    报表数据录入
    +
    &#xe7b5;
    +
  • + +
  • + +
    播放5
    +
    &#xe656;
    +
  • + +
  • + +
    清空@3x
    +
    &#xe64f;
    +
  • + +
  • + +
    删除
    +
    &#xe64e;
    +
  • + +
  • + +
    new-document-worksheet
    +
    &#xe792;
    +
  • + +
  • + +
    file-excel
    +
    &#xe7b7;
    +
  • + +
  • + +
    file-markdown
    +
    &#xe7b8;
    +
  • + +
  • + +
    file-word
    +
    &#xe7ba;
    +
  • + +
  • + +
    HTML5
    +
    &#xe87d;
    +
  • + +
  • + +
    HTML
    +
    &#xe64d;
    +
  • + +
  • + +
    pdf
    +
    &#xe67a;
    +
  • + +
  • + +
    个人用户
    +
    &#xe64c;
    +
  • + +
  • + +
    后台管理
    +
    &#xe64b;
    +
  • + +
  • + +
    字体代码
    +
    &#xec83;
    +
  • + +
  • + +
    版本
    +
    &#xe70c;
    +
  • + +
  • + +
    车位管理
    +
    &#xe73c;
    +
  • + +
  • + +
    dictate
    +
    &#xe64a;
    +
  • + +
  • + +
    circle-f
    +
    &#xe76a;
    +
  • + +
  • + +
    图表-函数
    +
    &#xe6fd;
    +
  • + +
  • + +
    视图管理器
    +
    &#xe647;
    +
  • + +
  • + +
    回车
    +
    &#xe643;
    +
  • + +
  • + +
    缺省
    +
    &#xe642;
    +
  • + +
  • + +
    进入箭头
    +
    &#xe88e;
    +
  • + +
  • + +
    右箭头
    +
    &#xe641;
    +
  • + +
  • + +
    向右箭头
    +
    &#xe660;
    +
  • + +
  • + +
    数据源
    +
    &#xe640;
    +
  • + +
  • + +
    question
    +
    &#xe67c;
    +
  • + +
  • + +
    星星-copy
    +
    &#xe63a;
    +
  • + +
  • + +
    控制台
    +
    &#xe69f;
    +
  • + +
  • + +
    星系
    +
    &#xe639;
    +
  • + +
  • + +
    暂无数据 (1)
    +
    &#xe638;
    +
  • + +
  • + +
    开始
    +
    &#xe637;
    +
  • + +
  • + +
    关闭
    +
    &#xe634;
    +
  • + +
  • + +
    下箭头
    +
    &#xeb6d;
    +
  • + +
  • + +
    more
    +
    &#xe633;
    +
  • + +
  • + +
    设置
    +
    &#xe630;
    +
  • + +
  • + +
    对话-未选
    +
    &#xe628;
    +
  • + +
  • + +
    图表-未选
    +
    &#xe629;
    +
  • + +
  • + +
    编组 13备份 3
    +
    &#xe62b;
    +
  • + +
  • + +
    编组备份
    +
    &#xe616;
    +
  • + +
  • + +
    表格
    +
    &#xe618;
    +
  • + +
  • + +
    收藏 (1)
    +
    &#xe61d;
    +
  • + +
  • + +
    guthub-未选
    +
    &#xe621;
    +
  • + +
  • + +
    数据-未选
    +
    &#xe622;
    +
  • + +
  • + +
    编组 4
    +
    &#xe624;
    +
  • + +
  • + +
    编组 14备份
    +
    &#xe627;
    +
  • + +
  • + +
    guthub-未选
    +
    &#xe615;
    +
  • + +
  • + +
    24gl-folderMinus
    +
    &#xeabe;
    +
  • + +
  • +  +
    24gl-folderOpen
    +
    &#xeabf;
    +
  • + +
  • + +
    24gf-folderOpen
    +
    &#xeac7;
    +
  • + +
  • + +
    云数据库
    +
    &#xe744;
    +
  • + +
  • + +
    报表
    +
    &#xe612;
    +
  • + +
  • + +
    工作台
    +
    &#xe614;
    +
  • + +
  • + +
    mongodb
    +
    &#xec21;
    +
  • + +
  • + +
    Redis
    +
    &#xe6a2;
    +
  • + +
  • + +
    HIVE_2
    +
    &#xe60e;
    +
  • + +
  • + +
    Kingbase
    +
    &#xe6a0;
    +
  • + +
  • + +
    仪表盘
    +
    &#xe60d;
    +
  • + +
  • + +
    presto
    +
    &#xe60b;
    +
  • + +
  • + +
    DB2
    +
    &#xe60a;
    +
  • + +
  • + +
    oceanbase
    +
    &#xe982;
    +
  • + +
  • + +
    达梦
    +
    &#xe655;
    +
  • + +
  • + +
    proxy
    +
    &#xe63f;
    +
  • + +
  • + +
    openai
    +
    &#xe646;
    +
  • + +
  • + +
    关于
    +
    &#xe60c;
    +
  • + +
  • + +
    衣服
    +
    &#xe666;
    +
  • + +
  • + +
    数据库
    +
    &#xe609;
    +
  • + +
  • + +
    数据源配置
    +
    &#xe649;
    +
  • + +
  • + +
    服务器_数据库_jurassic
    +
    &#xe6a6;
    +
  • + +
  • + +
    数据库
    +
    &#xe607;
    +
  • + +
  • + +
    数据库
    +
    &#xe625;
    +
  • + +
  • + +
    数据库数据
    +
    &#xe63c;
    +
  • + +
  • + +
    数据库
    +
    &#xe636;
    +
  • + +
  • + +
    配置数据源
    +
    &#xe62f;
    +
  • + +
  • + +
    SQL历史查询
    +
    &#xe80a;
    +
  • + +
  • + +
    重命名
    +
    &#xe623;
    +
  • + +
  • + +
    ico_数据查询与统计_预约情况查询
    +
    &#xe8ff;
    +
  • + +
  • + +
    clickhouse-云数据库ClickHouse
    +
    &#xe8f4;
    +
  • + +
  • + +
    rds_mariadb
    +
    &#xe6f5;
    +
  • + +
  • + +
    减少减去减号
    +
    &#xe62a;
    +
  • + +
  • + +
    sqlserver
    +
    &#xe664;
    +
  • + +
  • + +
    sqlite
    +
    &#xe65a;
    +
  • + +
  • + +
    缺省页_暂无数据
    +
    &#xe760;
    +
  • + +
  • + +
    未完成
    +
    &#xe755;
    +
  • + +
  • + +
    完成-01
    +
    &#xe62e;
    +
  • + +
  • + +
    成功
    +
    &#xe620;
    +
  • + +
  • + +
    机器人
    +
    &#xe70e;
    +
  • + +
  • + +
    换一换
    +
    &#xe635;
    +
  • + +
  • + +
    icon_infomation
    +
    &#xe65b;
    +
  • + +
  • + +
    key
    +
    &#xe775;
    +
  • + +
  • + +
    mysql
    +
    &#xec6d;
    +
  • + +
  • + +
    oracle
    +
    &#xec48;
    +
  • + +
  • + +
    postgresql
    +
    &#xec5d;
    +
  • + +
  • + +
    h2
    +
    &#xe61c;
    +
  • + +
  • + +
    cc-schema
    +
    &#xe696;
    +
  • + +
  • + +
    新建表格
    +
    &#xe6b6;
    +
  • + +
  • + +
    export
    +
    &#xe613;
    +
  • + +
  • + +
    角色管理
    +
    &#xe66d;
    +
  • + +
  • + +
    console
    +
    &#xe619;
    +
  • + +
  • + +
    24gf-folderMinus
    +
    &#xeac5;
    +
  • + +
  • + +
    查看
    +
    &#xe606;
    +
  • + +
  • + +
    复制_o
    +
    &#xeb4e;
    +
  • + +
  • + +
    执行
    +
    &#xe626;
    +
  • + +
  • + +
    m-格式化文字
    +
    &#xe7f8;
    +
  • + +
  • + +
    github-fill
    +
    &#xe885;
    +
  • + +
  • + +
    保存
    +
    &#xe645;
    +
  • + +
  • + +
    箭头_向左两次_o
    +
    &#xeb93;
    +
  • + +
  • + +
    新建窗口
    +
    &#xe603;
    +
  • + +
  • + +
    loading
    +
    &#xe6cd;
    +
  • + +
  • + +
    链接克隆
    +
    &#xe6ca;
    +
  • + +
  • + +
    SQL升级文件
    +
    &#xe63b;
    +
  • + +
  • + +
    sql
    +
    &#xe610;
    +
  • + +
  • + +
    连接流
    +
    &#xec57;
    +
  • + +
  • + +
    跳转/退出
    +
    &#xe685;
    +
  • + +
  • + +
    key
    +
    &#xe648;
    +
  • + +
  • + +
    播放记录
    +
    &#xe8ad;
    +
  • + +
  • + +
    成功
    +
    &#xe605;
    +
  • + +
  • + +
    失败
    +
    &#xe87c;
    +
  • + +
  • + +
    收回 上下
    +
    &#xe790;
    +
  • + +
  • + +
    展开 上下
    +
    &#xe7b1;
    +
  • + +
  • + +
    数据库
    +
    &#xe62c;
    +
  • + +
  • + +
    保存
    +
    &#xe936;
    +
  • + +
  • + +
    查询
    +
    &#xec4c;
    +
  • + +
  • + +
    对勾
    +
    &#xe61f;
    +
  • + +
  • + +
    check
    +
    &#xe617;
    +
  • + +
  • + +
    概览
    +
    &#xe632;
    +
  • + +
  • + +
    概览
    +
    &#xe63d;
    +
  • + +
  • + +
    编辑
    +
    &#xe602;
    +
  • + +
  • + +
    刷新
    +
    &#xec08;
    +
  • + +
  • + +
    菜单/列表
    +
    &#xe611;
    +
  • + +
  • + +
    表格
    +
    &#xe63e;
    +
  • + +
  • + +
    展开
    +
    &#xe65f;
    +
  • + +
  • + +
    收起
    +
    &#xe61e;
    +
  • + +
  • + +
    主题_o
    +
    &#xeb6f;
    +
  • + +
  • + +
    断开连接
    +
    &#xe65e;
    +
  • + +
  • + +
    修改
    +
    &#xe60f;
    +
  • + +
  • + +
    删除
    +
    &#xe604;
    +
  • + +
  • + +
    更多
    +
    &#xe601;
    +
  • + +
  • + +
    减少
    +
    &#xe644;
    +
  • + +
  • + +
    +
    &#xe61b;
    +
  • + +
  • + +
    加号
    +
    &#xe631;
    +
  • + +
  • + +
    arrow drop down
    +
    &#xe608;
    +
  • + +
  • + +
    search
    +
    &#xe600;
    +
  • + +
  • + +
    download
    +
    &#xe66c;
    +
  • + +
  • + +
    向右箭头
    +
    &#xe79c;
    +
  • + +
  • + +
    删除线型
    +
    &#xe6a7;
    +
  • + +
  • + +
    cross
    +
    &#xec8e;
    +
  • + +
  • + +
    刷新
    +
    &#xe62d;
    +
  • + +
  • + +
    提醒
    +
    &#xe913;
    +
  • + +
  • + +
    138设置、系统设置、功能设置、属性
    +
    &#xe795;
    +
  • + +
  • + +
    执行sql脚本
    +
    &#xe759;
    +
  • + +
  • + +
    虚拟数据库管理
    +
    &#xe61a;
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • +
+
+

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.woff2?t=1700381336886') format('woff2'),
+       url('iconfont.woff?t=1700381336886') format('woff'),
+       url('iconfont.ttf?t=1700381336886') format('truetype');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + database +
    +
    .icon-database +
    +
  • + +
  • + +
    + 筛选 +
    +
    .icon-shaixuan1 +
    +
  • + +
  • + +
    + 刷新 +
    +
    .icon-shuaxin2 +
    +
  • + +
  • + +
    + 加号_o +
    +
    .icon-jiahao_o +
    +
  • + +
  • + +
    + 数据库_jurassic +
    +
    .icon-jurassic_data +
    +
  • + +
  • + +
    + 权限 +
    +
    .icon-quanxian +
    +
  • + +
  • + +
    + sharpicons_add-database +
    +
    .icon-sharpicons_add-database +
    +
  • + +
  • + +
    + 组织管理 +
    +
    .icon-zuzhiguanli- +
    +
  • + +
  • + +
    + 空间 +
    +
    .icon-moxing-miaobian +
    +
  • + +
  • + +
    + 电话_填充 +
    +
    .icon-dianhuatianchong +
    +
  • + +
  • + +
    + 邮箱 +
    +
    .icon-youxiang +
    +
  • + +
  • + +
    + 微信 +
    +
    .icon-weixin +
    +
  • + +
  • + +
    + 谷歌 +
    +
    .icon-google +
    +
  • + +
  • + +
    + 下箭头-copy +
    +
    .icon-xiajiantou1-copy +
    +
  • + +
  • + +
    + 查看 +
    +
    .icon-chakan2 +
    +
  • + +
  • + +
    + clone +
    +
    .icon-clone +
    +
  • + +
  • + +
    + 提交 +
    +
    .icon-tijiao +
    +
  • + +
  • + +
    + 查看 +
    +
    .icon-chakan1 +
    +
  • + +
  • + +
    + 复制 +
    +
    .icon-fuzhi +
    +
  • + +
  • + +
    + icon_answer +
    +
    .icon-icon_answer +
    +
  • + +
  • + +
    + icon_question +
    +
    .icon-icon_question +
    +
  • + +
  • + +
    + 发送 +
    +
    .icon-fasong +
    +
  • + +
  • + +
    + 重启 +
    +
    .icon-zhongqi +
    +
  • + +
  • + +
    + 提醒 +
    +
    .icon-tixing2 +
    +
  • + +
  • + +
    + 提醒 +
    +
    .icon-tixing3 +
    +
  • + +
  • + +
    + 提醒 +
    +
    .icon-tixing1 +
    +
  • + +
  • + +
    + 升级 +
    +
    .icon-shengji +
    +
  • + +
  • + +
    + 全局_升级 +
    +
    .icon-quanju_shengji +
    +
  • + +
  • + +
    + 关于我们 +
    +
    .icon-guanyuwomen1 +
    +
  • + +
  • + +
    + ico版本更新 +
    +
    .icon-icobanbengengxin +
    +
  • + +
  • + +
    + 对话气泡 +
    +
    .icon-duihuaqipao +
    +
  • + +
  • + +
    + 角色权限 +
    +
    .icon-jiaosequanxian +
    +
  • + +
  • + +
    + preview +
    +
    .icon-preview1 +
    +
  • + +
  • + +
    + 导入 +
    +
    .icon-daoru +
    +
  • + +
  • + +
    + 终止 +
    +
    .icon-zhongzhi +
    +
  • + +
  • + +
    + 退出 +
    +
    .icon-tuichu +
    +
  • + +
  • + +
    + 控桩终端 +
    +
    .icon-kongzhuangzhongduan +
    +
  • + +
  • + +
    + 撤销 +
    +
    .icon-chexiao1 +
    +
  • + +
  • + +
    + 向上 +
    +
    .icon-xiangshang +
    +
  • + +
  • + +
    + 查看 +
    +
    .icon-chakan-copy +
    +
  • + +
  • + +
    + 编辑数据_编辑录入操作_jurassic +
    +
    .icon-jurassic_edit-data +
    +
  • + +
  • + +
    + 编辑表格_编辑录入操作_jurassic +
    +
    .icon-jurassic_edit-table +
    +
  • + +
  • + +
    + 报表数据录入 +
    +
    .icon-baobiaoshujuluru +
    +
  • + +
  • + +
    + 播放5 +
    +
    .icon-bofang5 +
    +
  • + +
  • + +
    + 清空@3x +
    +
    .icon-a-qingkong3x +
    +
  • + +
  • + +
    + 删除 +
    +
    .icon-shanchu +
    +
  • + +
  • + +
    + new-document-worksheet +
    +
    .icon-newdocumentworksheet +
    +
  • + +
  • + +
    + file-excel +
    +
    .icon-file-excel +
    +
  • + +
  • + +
    + file-markdown +
    +
    .icon-file-markdown +
    +
  • + +
  • + +
    + file-word +
    +
    .icon-file-word +
    +
  • + +
  • + +
    + HTML5 +
    +
    .icon-HTML +
    +
  • + +
  • + +
    + HTML +
    +
    .icon-HTML1 +
    +
  • + +
  • + +
    + pdf +
    +
    .icon-pdf +
    +
  • + +
  • + +
    + 个人用户 +
    +
    .icon-gerenyonghu +
    +
  • + +
  • + +
    + 后台管理 +
    +
    .icon-houtaiguanli +
    +
  • + +
  • + +
    + 字体代码 +
    +
    .icon-zitidaima +
    +
  • + +
  • + +
    + 版本 +
    +
    .icon-banben +
    +
  • + +
  • + +
    + 车位管理 +
    +
    .icon-cheweiguanli +
    +
  • + +
  • + +
    + dictate +
    +
    .icon-dianzhelidaochu +
    +
  • + +
  • + +
    + circle-f +
    +
    .icon-circle-f +
    +
  • + +
  • + +
    + 图表-函数 +
    +
    .icon-tubiao-hanshu +
    +
  • + +
  • + +
    + 视图管理器 +
    +
    .icon-shituguanliqi +
    +
  • + +
  • + +
    + 回车 +
    +
    .icon-huiche +
    +
  • + +
  • + +
    + 缺省 +
    +
    .icon-quesheng +
    +
  • + +
  • + +
    + 进入箭头 +
    +
    .icon-jinrujiantou +
    +
  • + +
  • + +
    + 右箭头 +
    +
    .icon-youjiantou_huaban +
    +
  • + +
  • + +
    + 向右箭头 +
    +
    .icon-xiangyoujiantou1 +
    +
  • + +
  • + +
    + 数据源 +
    +
    .icon-shujuyuan +
    +
  • + +
  • + +
    + question +
    +
    .icon-question +
    +
  • + +
  • + +
    + 星星-copy +
    +
    .icon-xingxing +
    +
  • + +
  • + +
    + 控制台 +
    +
    .icon-kongzhitai +
    +
  • + +
  • + +
    + 星系 +
    +
    .icon-xingxi +
    +
  • + +
  • + +
    + 暂无数据 (1) +
    +
    .icon-a-zanwushuju1 +
    +
  • + +
  • + +
    + 开始 +
    +
    .icon-kaishi +
    +
  • + +
  • + +
    + 关闭 +
    +
    .icon-guanbi +
    +
  • + +
  • + +
    + 下箭头 +
    +
    .icon-xiajiantou +
    +
  • + +
  • + +
    + more +
    +
    .icon-gengduo +
    +
  • + +
  • + +
    + 设置 +
    +
    .icon-shezhi +
    +
  • + +
  • + +
    + 对话-未选 +
    +
    .icon-duihua-weixuan +
    +
  • + +
  • + +
    + 图表-未选 +
    +
    .icon-tubiao-weixuan +
    +
  • + +
  • + +
    + 编组 13备份 3 +
    +
    .icon-a-bianzu13beifen3 +
    +
  • + +
  • + +
    + 编组备份 +
    +
    .icon-bianzubeifen +
    +
  • + +
  • + +
    + 表格 +
    +
    .icon-biaoge1 +
    +
  • + +
  • + +
    + 收藏 (1) +
    +
    .icon-a-shoucang1 +
    +
  • + +
  • + +
    + guthub-未选 +
    +
    .icon-guthub-weixuan1 +
    +
  • + +
  • + +
    + 数据-未选 +
    +
    .icon-shuju-weixuan +
    +
  • + +
  • + +
    + 编组 4 +
    +
    .icon-a-bianzu4 +
    +
  • + +
  • + +
    + 编组 14备份 +
    +
    .icon-a-bianzu14beifen +
    +
  • + +
  • + +
    + guthub-未选 +
    +
    .icon-guthub-weixuan +
    +
  • + +
  • + +
    + 24gl-folderMinus +
    +
    .icon-24gl-folderMinus +
    +
  • + +
  • + +
    + 24gl-folderOpen +
    +
    .icon-24gl-folderOpen +
    +
  • + +
  • + +
    + 24gf-folderOpen +
    +
    .icon-24gf-folderOpen +
    +
  • + +
  • + +
    + 云数据库 +
    +
    .icon-yunshujuku +
    +
  • + +
  • + +
    + 报表 +
    +
    .icon-baobiao +
    +
  • + +
  • + +
    + 工作台 +
    +
    .icon-gongzuotai +
    +
  • + +
  • + +
    + mongodb +
    +
    .icon-mongodb +
    +
  • + +
  • + +
    + Redis +
    +
    .icon-Redis +
    +
  • + +
  • + +
    + HIVE_2 +
    +
    .icon-HIVE +
    +
  • + +
  • + +
    + Kingbase +
    +
    .icon-Kingbase +
    +
  • + +
  • + +
    + 仪表盘 +
    +
    .icon-yibiaopan +
    +
  • + +
  • + +
    + presto +
    +
    .icon-presto_sql +
    +
  • + +
  • + +
    + DB2 +
    +
    .icon-shujukuleixingtubiao-kuozhan- +
    +
  • + +
  • + +
    + oceanbase +
    +
    .icon-oceanbase +
    +
  • + +
  • + +
    + 达梦 +
    +
    .icon-dameng1 +
    +
  • + +
  • + +
    + proxy +
    +
    .icon-proxy +
    +
  • + +
  • + +
    + openai +
    +
    .icon-openai +
    +
  • + +
  • + +
    + 关于 +
    +
    .icon-guanyu +
    +
  • + +
  • + +
    + 衣服 +
    +
    .icon-yifu +
    +
  • + +
  • + +
    + 数据库 +
    +
    .icon-shujuku4 +
    +
  • + +
  • + +
    + 数据源配置 +
    +
    .icon-shujuyuanpeizhi +
    +
  • + +
  • + +
    + 服务器_数据库_jurassic +
    +
    .icon-jurassic_server +
    +
  • + +
  • + +
    + 数据库 +
    +
    .icon-shujuku2 +
    +
  • + +
  • + +
    + 数据库 +
    +
    .icon-shujuku3 +
    +
  • + +
  • + +
    + 数据库数据 +
    +
    .icon-shujukushuju +
    +
  • + +
  • + +
    + 数据库 +
    +
    .icon-shujuku1 +
    +
  • + +
  • + +
    + 配置数据源 +
    +
    .icon-peizhishujuyuan +
    +
  • + +
  • + +
    + SQL历史查询 +
    +
    .icon-SQLlishichaxun +
    +
  • + +
  • + +
    + 重命名 +
    +
    .icon-zhongmingming +
    +
  • + +
  • + +
    + ico_数据查询与统计_预约情况查询 +
    +
    .icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun +
    +
  • + +
  • + +
    + clickhouse-云数据库ClickHouse +
    +
    .icon-clickhouse-yunshujukuClickHouse +
    +
  • + +
  • + +
    + rds_mariadb +
    +
    .icon-rds_mariadb +
    +
  • + +
  • + +
    + 减少减去减号 +
    +
    .icon-jianshaojianqujianhao +
    +
  • + +
  • + +
    + sqlserver +
    +
    .icon-sqlserver +
    +
  • + +
  • + +
    + sqlite +
    +
    .icon-sqlite +
    +
  • + +
  • + +
    + 缺省页_暂无数据 +
    +
    .icon-queshengye_zanwushuju +
    +
  • + +
  • + +
    + 未完成 +
    +
    .icon-weiwancheng +
    +
  • + +
  • + +
    + 完成-01 +
    +
    .icon-wancheng- +
    +
  • + +
  • + +
    + 成功 +
    +
    .icon-chenggong1 +
    +
  • + +
  • + +
    + 机器人 +
    +
    .icon-jiqiren +
    +
  • + +
  • + +
    + 换一换 +
    +
    .icon-huanyihuan +
    +
  • + +
  • + +
    + icon_infomation +
    +
    .icon-icon_infomation +
    +
  • + +
  • + +
    + key +
    +
    .icon-key1 +
    +
  • + +
  • + +
    + mysql +
    +
    .icon-mysql +
    +
  • + +
  • + +
    + oracle +
    +
    .icon-oracle +
    +
  • + +
  • + +
    + postgresql +
    +
    .icon-postgresql +
    +
  • + +
  • + +
    + h2 +
    +
    .icon-h2 +
    +
  • + +
  • + +
    + cc-schema +
    +
    .icon-cc-schema +
    +
  • + +
  • + +
    + 新建表格 +
    +
    .icon-xinjianbiaoge +
    +
  • + +
  • + +
    + export +
    +
    .icon-export +
    +
  • + +
  • + +
    + 角色管理 +
    +
    .icon-jiaoseguanli +
    +
  • + +
  • + +
    + console +
    +
    .icon-console +
    +
  • + +
  • + +
    + 24gf-folderMinus +
    +
    .icon-24gf-folderMinus +
    +
  • + +
  • + +
    + 查看 +
    +
    .icon-chakan +
    +
  • + +
  • + +
    + 复制_o +
    +
    .icon-fuzhi_o +
    +
  • + +
  • + +
    + 执行 +
    +
    .icon-zhihang +
    +
  • + +
  • + +
    + m-格式化文字 +
    +
    .icon-m-geshihuawenzi +
    +
  • + +
  • + +
    + github-fill +
    +
    .icon-github-fill +
    +
  • + +
  • + +
    + 保存 +
    +
    .icon-baocun2 +
    +
  • + +
  • + +
    + 箭头_向左两次_o +
    +
    .icon-jiantou_xiangzuoliangci_o +
    +
  • + +
  • + +
    + 新建窗口 +
    +
    .icon-xinjianchuangkou +
    +
  • + +
  • + +
    + loading +
    +
    .icon-loading2 +
    +
  • + +
  • + +
    + 链接克隆 +
    +
    .icon-lianjiekelong +
    +
  • + +
  • + +
    + SQL升级文件 +
    +
    .icon-SQLshengjiwenjian +
    +
  • + +
  • + +
    + sql +
    +
    .icon-sql +
    +
  • + +
  • + +
    + 连接流 +
    +
    .icon-lianjieliu +
    +
  • + +
  • + +
    + 跳转/退出 +
    +
    .icon-tiaozhuan +
    +
  • + +
  • + +
    + key +
    +
    .icon-key +
    +
  • + +
  • + +
    + 播放记录 +
    +
    .icon-bofangjilu +
    +
  • + +
  • + +
    + 成功 +
    +
    .icon-chenggong +
    +
  • + +
  • + +
    + 失败 +
    +
    .icon-shibai +
    +
  • + +
  • + +
    + 收回 上下 +
    +
    .icon-shouhuishangxia +
    +
  • + +
  • + +
    + 展开 上下 +
    +
    .icon-zhankaishangxia +
    +
  • + +
  • + +
    + 数据库 +
    +
    .icon-shujuku +
    +
  • + +
  • + +
    + 保存 +
    +
    .icon-baocun +
    +
  • + +
  • + +
    + 查询 +
    +
    .icon-chaxun +
    +
  • + +
  • + +
    + 对勾 +
    +
    .icon-duigou11 +
    +
  • + +
  • + +
    + check +
    +
    .icon-check1 +
    +
  • + +
  • + +
    + 概览 +
    +
    .icon-gailan +
    +
  • + +
  • + +
    + 概览 +
    +
    .icon-huaban2 +
    +
  • + +
  • + +
    + 编辑 +
    +
    .icon-bianji +
    +
  • + +
  • + +
    + 刷新 +
    +
    .icon-shuaxin1 +
    +
  • + +
  • + +
    + 菜单/列表 +
    +
    .icon-caidan +
    +
  • + +
  • + +
    + 表格 +
    +
    .icon-biaoge +
    +
  • + +
  • + +
    + 展开 +
    +
    .icon-zhankai +
    +
  • + +
  • + +
    + 收起 +
    +
    .icon-shouqi +
    +
  • + +
  • + +
    + 主题_o +
    +
    .icon-zhuti_o +
    +
  • + +
  • + +
    + 断开连接 +
    +
    .icon-duankailianjie +
    +
  • + +
  • + +
    + 修改 +
    +
    .icon-xiugai +
    +
  • + +
  • + +
    + 删除 +
    +
    .icon-delete +
    +
  • + +
  • + +
    + 更多 +
    +
    .icon-gengduo1 +
    +
  • + +
  • + +
    + 减少 +
    +
    .icon-jianshao +
    +
  • + +
  • + +
    + 加 +
    +
    .icon-jia +
    +
  • + +
  • + +
    + 加号 +
    +
    .icon-hao +
    +
  • + +
  • + +
    + arrow drop down +
    +
    .icon-right +
    +
  • + +
  • + +
    + search +
    +
    .icon-search1 +
    +
  • + +
  • + +
    + download +
    +
    .icon-download1 +
    +
  • + +
  • + +
    + 向右箭头 +
    +
    .icon-xiangyoujiantou +
    +
  • + +
  • + +
    + 删除线型 +
    +
    .icon-shanchuxianxing +
    +
  • + +
  • + +
    + cross +
    +
    .icon-cross-copy +
    +
  • + +
  • + +
    + 刷新 +
    +
    .icon-shuaxin +
    +
  • + +
  • + +
    + 提醒 +
    +
    .icon-tixing +
    +
  • + +
  • + +
    + 138设置、系统设置、功能设置、属性 +
    +
    .icon-shezhixitongshezhigongnengshezhishuxing +
    +
  • + +
  • + +
    + 执行sql脚本 +
    +
    .icon-zhihangsqljiaoben +
    +
  • + +
  • + +
    + 虚拟数据库管理 +
    +
    .icon-xunishujukuguanli +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont icon-xxx"></span>
+
+
+

" + iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    database
    +
    #icon-database
    +
  • + +
  • + +
    筛选
    +
    #icon-shaixuan1
    +
  • + +
  • + +
    刷新
    +
    #icon-shuaxin2
    +
  • + +
  • + +
    加号_o
    +
    #icon-jiahao_o
    +
  • + +
  • + +
    数据库_jurassic
    +
    #icon-jurassic_data
    +
  • + +
  • + +
    权限
    +
    #icon-quanxian
    +
  • + +
  • + +
    sharpicons_add-database
    +
    #icon-sharpicons_add-database
    +
  • + +
  • + +
    组织管理
    +
    #icon-zuzhiguanli-
    +
  • + +
  • + +
    空间
    +
    #icon-moxing-miaobian
    +
  • + +
  • + +
    电话_填充
    +
    #icon-dianhuatianchong
    +
  • + +
  • + +
    邮箱
    +
    #icon-youxiang
    +
  • + +
  • + +
    微信
    +
    #icon-weixin
    +
  • + +
  • + +
    谷歌
    +
    #icon-google
    +
  • + +
  • + +
    下箭头-copy
    +
    #icon-xiajiantou1-copy
    +
  • + +
  • + +
    查看
    +
    #icon-chakan2
    +
  • + +
  • + +
    clone
    +
    #icon-clone
    +
  • + +
  • + +
    提交
    +
    #icon-tijiao
    +
  • + +
  • + +
    查看
    +
    #icon-chakan1
    +
  • + +
  • + +
    复制
    +
    #icon-fuzhi
    +
  • + +
  • + +
    icon_answer
    +
    #icon-icon_answer
    +
  • + +
  • + +
    icon_question
    +
    #icon-icon_question
    +
  • + +
  • + +
    发送
    +
    #icon-fasong
    +
  • + +
  • + +
    重启
    +
    #icon-zhongqi
    +
  • + +
  • + +
    提醒
    +
    #icon-tixing2
    +
  • + +
  • + +
    提醒
    +
    #icon-tixing3
    +
  • + +
  • + +
    提醒
    +
    #icon-tixing1
    +
  • + +
  • + +
    升级
    +
    #icon-shengji
    +
  • + +
  • + +
    全局_升级
    +
    #icon-quanju_shengji
    +
  • + +
  • + +
    关于我们
    +
    #icon-guanyuwomen1
    +
  • + +
  • + +
    ico版本更新
    +
    #icon-icobanbengengxin
    +
  • + +
  • + +
    对话气泡
    +
    #icon-duihuaqipao
    +
  • + +
  • + +
    角色权限
    +
    #icon-jiaosequanxian
    +
  • + +
  • + +
    preview
    +
    #icon-preview1
    +
  • + +
  • + +
    导入
    +
    #icon-daoru
    +
  • + +
  • + +
    终止
    +
    #icon-zhongzhi
    +
  • + +
  • + +
    退出
    +
    #icon-tuichu
    +
  • + +
  • + +
    控桩终端
    +
    #icon-kongzhuangzhongduan
    +
  • + +
  • + +
    撤销
    +
    #icon-chexiao1
    +
  • + +
  • + +
    向上
    +
    #icon-xiangshang
    +
  • + +
  • + +
    查看
    +
    #icon-chakan-copy
    +
  • + +
  • + +
    编辑数据_编辑录入操作_jurassic
    +
    #icon-jurassic_edit-data
    +
  • + +
  • + +
    编辑表格_编辑录入操作_jurassic
    +
    #icon-jurassic_edit-table
    +
  • + +
  • + +
    报表数据录入
    +
    #icon-baobiaoshujuluru
    +
  • + +
  • + +
    播放5
    +
    #icon-bofang5
    +
  • + +
  • + +
    清空@3x
    +
    #icon-a-qingkong3x
    +
  • + +
  • + +
    删除
    +
    #icon-shanchu
    +
  • + +
  • + +
    new-document-worksheet
    +
    #icon-newdocumentworksheet
    +
  • + +
  • + +
    file-excel
    +
    #icon-file-excel
    +
  • + +
  • + +
    file-markdown
    +
    #icon-file-markdown
    +
  • + +
  • + +
    file-word
    +
    #icon-file-word
    +
  • + +
  • + +
    HTML5
    +
    #icon-HTML
    +
  • + +
  • + +
    HTML
    +
    #icon-HTML1
    +
  • + +
  • + +
    pdf
    +
    #icon-pdf
    +
  • + +
  • + +
    个人用户
    +
    #icon-gerenyonghu
    +
  • + +
  • + +
    后台管理
    +
    #icon-houtaiguanli
    +
  • + +
  • + +
    字体代码
    +
    #icon-zitidaima
    +
  • + +
  • + +
    版本
    +
    #icon-banben
    +
  • + +
  • + +
    车位管理
    +
    #icon-cheweiguanli
    +
  • + +
  • + +
    dictate
    +
    #icon-dianzhelidaochu
    +
  • + +
  • + +
    circle-f
    +
    #icon-circle-f
    +
  • + +
  • + +
    图表-函数
    +
    #icon-tubiao-hanshu
    +
  • + +
  • + +
    视图管理器
    +
    #icon-shituguanliqi
    +
  • + +
  • + +
    回车
    +
    #icon-huiche
    +
  • + +
  • + +
    缺省
    +
    #icon-quesheng
    +
  • + +
  • + +
    进入箭头
    +
    #icon-jinrujiantou
    +
  • + +
  • + +
    右箭头
    +
    #icon-youjiantou_huaban
    +
  • + +
  • + +
    向右箭头
    +
    #icon-xiangyoujiantou1
    +
  • + +
  • + +
    数据源
    +
    #icon-shujuyuan
    +
  • + +
  • + +
    question
    +
    #icon-question
    +
  • + +
  • + +
    星星-copy
    +
    #icon-xingxing
    +
  • + +
  • + +
    控制台
    +
    #icon-kongzhitai
    +
  • + +
  • + +
    星系
    +
    #icon-xingxi
    +
  • + +
  • + +
    暂无数据 (1)
    +
    #icon-a-zanwushuju1
    +
  • + +
  • + +
    开始
    +
    #icon-kaishi
    +
  • + +
  • + +
    关闭
    +
    #icon-guanbi
    +
  • + +
  • + +
    下箭头
    +
    #icon-xiajiantou
    +
  • + +
  • + +
    more
    +
    #icon-gengduo
    +
  • + +
  • + +
    设置
    +
    #icon-shezhi
    +
  • + +
  • + +
    对话-未选
    +
    #icon-duihua-weixuan
    +
  • + +
  • + +
    图表-未选
    +
    #icon-tubiao-weixuan
    +
  • + +
  • + +
    编组 13备份 3
    +
    #icon-a-bianzu13beifen3
    +
  • + +
  • + +
    编组备份
    +
    #icon-bianzubeifen
    +
  • + +
  • + +
    表格
    +
    #icon-biaoge1
    +
  • + +
  • + +
    收藏 (1)
    +
    #icon-a-shoucang1
    +
  • + +
  • + +
    guthub-未选
    +
    #icon-guthub-weixuan1
    +
  • + +
  • + +
    数据-未选
    +
    #icon-shuju-weixuan
    +
  • + +
  • + +
    编组 4
    +
    #icon-a-bianzu4
    +
  • + +
  • + +
    编组 14备份
    +
    #icon-a-bianzu14beifen
    +
  • + +
  • + +
    guthub-未选
    +
    #icon-guthub-weixuan
    +
  • + +
  • + +
    24gl-folderMinus
    +
    #icon-24gl-folderMinus
    +
  • + +
  • + +
    24gl-folderOpen
    +
    #icon-24gl-folderOpen
    +
  • + +
  • + +
    24gf-folderOpen
    +
    #icon-24gf-folderOpen
    +
  • + +
  • + +
    云数据库
    +
    #icon-yunshujuku
    +
  • + +
  • + +
    报表
    +
    #icon-baobiao
    +
  • + +
  • + +
    工作台
    +
    #icon-gongzuotai
    +
  • + +
  • + +
    mongodb
    +
    #icon-mongodb
    +
  • + +
  • + +
    Redis
    +
    #icon-Redis
    +
  • + +
  • + +
    HIVE_2
    +
    #icon-HIVE
    +
  • + +
  • + +
    Kingbase
    +
    #icon-Kingbase
    +
  • + +
  • + +
    仪表盘
    +
    #icon-yibiaopan
    +
  • + +
  • + +
    presto
    +
    #icon-presto_sql
    +
  • + +
  • + +
    DB2
    +
    #icon-shujukuleixingtubiao-kuozhan-
    +
  • + +
  • + +
    oceanbase
    +
    #icon-oceanbase
    +
  • + +
  • + +
    达梦
    +
    #icon-dameng1
    +
  • + +
  • + +
    proxy
    +
    #icon-proxy
    +
  • + +
  • + +
    openai
    +
    #icon-openai
    +
  • + +
  • + +
    关于
    +
    #icon-guanyu
    +
  • + +
  • + +
    衣服
    +
    #icon-yifu
    +
  • + +
  • + +
    数据库
    +
    #icon-shujuku4
    +
  • + +
  • + +
    数据源配置
    +
    #icon-shujuyuanpeizhi
    +
  • + +
  • + +
    服务器_数据库_jurassic
    +
    #icon-jurassic_server
    +
  • + +
  • + +
    数据库
    +
    #icon-shujuku2
    +
  • + +
  • + +
    数据库
    +
    #icon-shujuku3
    +
  • + +
  • + +
    数据库数据
    +
    #icon-shujukushuju
    +
  • + +
  • + +
    数据库
    +
    #icon-shujuku1
    +
  • + +
  • + +
    配置数据源
    +
    #icon-peizhishujuyuan
    +
  • + +
  • + +
    SQL历史查询
    +
    #icon-SQLlishichaxun
    +
  • + +
  • + +
    重命名
    +
    #icon-zhongmingming
    +
  • + +
  • + +
    ico_数据查询与统计_预约情况查询
    +
    #icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun
    +
  • + +
  • + +
    clickhouse-云数据库ClickHouse
    +
    #icon-clickhouse-yunshujukuClickHouse
    +
  • + +
  • + +
    rds_mariadb
    +
    #icon-rds_mariadb
    +
  • + +
  • + +
    减少减去减号
    +
    #icon-jianshaojianqujianhao
    +
  • + +
  • + +
    sqlserver
    +
    #icon-sqlserver
    +
  • + +
  • + +
    sqlite
    +
    #icon-sqlite
    +
  • + +
  • + +
    缺省页_暂无数据
    +
    #icon-queshengye_zanwushuju
    +
  • + +
  • + +
    未完成
    +
    #icon-weiwancheng
    +
  • + +
  • + +
    完成-01
    +
    #icon-wancheng-
    +
  • + +
  • + +
    成功
    +
    #icon-chenggong1
    +
  • + +
  • + +
    机器人
    +
    #icon-jiqiren
    +
  • + +
  • + +
    换一换
    +
    #icon-huanyihuan
    +
  • + +
  • + +
    icon_infomation
    +
    #icon-icon_infomation
    +
  • + +
  • + +
    key
    +
    #icon-key1
    +
  • + +
  • + +
    mysql
    +
    #icon-mysql
    +
  • + +
  • + +
    oracle
    +
    #icon-oracle
    +
  • + +
  • + +
    postgresql
    +
    #icon-postgresql
    +
  • + +
  • + +
    h2
    +
    #icon-h2
    +
  • + +
  • + +
    cc-schema
    +
    #icon-cc-schema
    +
  • + +
  • + +
    新建表格
    +
    #icon-xinjianbiaoge
    +
  • + +
  • + +
    export
    +
    #icon-export
    +
  • + +
  • + +
    角色管理
    +
    #icon-jiaoseguanli
    +
  • + +
  • + +
    console
    +
    #icon-console
    +
  • + +
  • + +
    24gf-folderMinus
    +
    #icon-24gf-folderMinus
    +
  • + +
  • + +
    查看
    +
    #icon-chakan
    +
  • + +
  • + +
    复制_o
    +
    #icon-fuzhi_o
    +
  • + +
  • + +
    执行
    +
    #icon-zhihang
    +
  • + +
  • + +
    m-格式化文字
    +
    #icon-m-geshihuawenzi
    +
  • + +
  • + +
    github-fill
    +
    #icon-github-fill
    +
  • + +
  • + +
    保存
    +
    #icon-baocun2
    +
  • + +
  • + +
    箭头_向左两次_o
    +
    #icon-jiantou_xiangzuoliangci_o
    +
  • + +
  • + +
    新建窗口
    +
    #icon-xinjianchuangkou
    +
  • + +
  • + +
    loading
    +
    #icon-loading2
    +
  • + +
  • + +
    链接克隆
    +
    #icon-lianjiekelong
    +
  • + +
  • + +
    SQL升级文件
    +
    #icon-SQLshengjiwenjian
    +
  • + +
  • + +
    sql
    +
    #icon-sql
    +
  • + +
  • + +
    连接流
    +
    #icon-lianjieliu
    +
  • + +
  • + +
    跳转/退出
    +
    #icon-tiaozhuan
    +
  • + +
  • + +
    key
    +
    #icon-key
    +
  • + +
  • + +
    播放记录
    +
    #icon-bofangjilu
    +
  • + +
  • + +
    成功
    +
    #icon-chenggong
    +
  • + +
  • + +
    失败
    +
    #icon-shibai
    +
  • + +
  • + +
    收回 上下
    +
    #icon-shouhuishangxia
    +
  • + +
  • + +
    展开 上下
    +
    #icon-zhankaishangxia
    +
  • + +
  • + +
    数据库
    +
    #icon-shujuku
    +
  • + +
  • + +
    保存
    +
    #icon-baocun
    +
  • + +
  • + +
    查询
    +
    #icon-chaxun
    +
  • + +
  • + +
    对勾
    +
    #icon-duigou11
    +
  • + +
  • + +
    check
    +
    #icon-check1
    +
  • + +
  • + +
    概览
    +
    #icon-gailan
    +
  • + +
  • + +
    概览
    +
    #icon-huaban2
    +
  • + +
  • + +
    编辑
    +
    #icon-bianji
    +
  • + +
  • + +
    刷新
    +
    #icon-shuaxin1
    +
  • + +
  • + +
    菜单/列表
    +
    #icon-caidan
    +
  • + +
  • + +
    表格
    +
    #icon-biaoge
    +
  • + +
  • + +
    展开
    +
    #icon-zhankai
    +
  • + +
  • + +
    收起
    +
    #icon-shouqi
    +
  • + +
  • + +
    主题_o
    +
    #icon-zhuti_o
    +
  • + +
  • + +
    断开连接
    +
    #icon-duankailianjie
    +
  • + +
  • + +
    修改
    +
    #icon-xiugai
    +
  • + +
  • + +
    删除
    +
    #icon-delete
    +
  • + +
  • + +
    更多
    +
    #icon-gengduo1
    +
  • + +
  • + +
    减少
    +
    #icon-jianshao
    +
  • + +
  • + +
    +
    #icon-jia
    +
  • + +
  • + +
    加号
    +
    #icon-hao
    +
  • + +
  • + +
    arrow drop down
    +
    #icon-right
    +
  • + +
  • + +
    search
    +
    #icon-search1
    +
  • + +
  • + +
    download
    +
    #icon-download1
    +
  • + +
  • + +
    向右箭头
    +
    #icon-xiangyoujiantou
    +
  • + +
  • + +
    删除线型
    +
    #icon-shanchuxianxing
    +
  • + +
  • + +
    cross
    +
    #icon-cross-copy
    +
  • + +
  • + +
    刷新
    +
    #icon-shuaxin
    +
  • + +
  • + +
    提醒
    +
    #icon-tixing
    +
  • + +
  • + +
    138设置、系统设置、功能设置、属性
    +
    #icon-shezhixitongshezhigongnengshezhishuxing
    +
  • + +
  • + +
    执行sql脚本
    +
    #icon-zhihangsqljiaoben
    +
  • + +
  • + +
    虚拟数据库管理
    +
    #icon-xunishujukuguanli
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/chat2db-client/src/assets/font/iconfont.css b/chat2db-client/src/assets/font/iconfont.css index d9336e5f2..8772b333e 100644 --- a/chat2db-client/src/assets/font/iconfont.css +++ b/chat2db-client/src/assets/font/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 3633546 */ - src: url('iconfont.woff2?t=1699180324907') format('woff2'), - url('iconfont.woff?t=1699180324907') format('woff'), - url('iconfont.ttf?t=1699180324907') format('truetype'); + src: url('iconfont.woff2?t=1700381336886') format('woff2'), + url('iconfont.woff?t=1700381336886') format('woff'), + url('iconfont.ttf?t=1700381336886') format('truetype'); } .iconfont { @@ -13,6 +13,58 @@ -moz-osx-font-smoothing: grayscale; } +.icon-database:before { + content: "\e669"; +} + +.icon-shaixuan1:before { + content: "\e888"; +} + +.icon-shuaxin2:before { + content: "\e668"; +} + +.icon-jiahao_o:before { + content: "\eb78"; +} + +.icon-jurassic_data:before { + content: "\e6a9"; +} + +.icon-quanxian:before { + content: "\e667"; +} + +.icon-sharpicons_add-database:before { + content: "\e816"; +} + +.icon-zuzhiguanli-:before { + content: "\e663"; +} + +.icon-moxing-miaobian:before { + content: "\e691"; +} + +.icon-dianhuatianchong:before { + content: "\e678"; +} + +.icon-youxiang:before { + content: "\e672"; +} + +.icon-weixin:before { + content: "\e65d"; +} + +.icon-google:before { + content: "\eaa8"; +} + .icon-xiajiantou1-copy:before { content: "\100be"; } diff --git a/chat2db-client/src/assets/font/iconfont.js b/chat2db-client/src/assets/font/iconfont.js index 4ed8dec3c..47294c08c 100644 --- a/chat2db-client/src/assets/font/iconfont.js +++ b/chat2db-client/src/assets/font/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_3633546='',function(h){var a=(a=document.getElementsByTagName("script"))[a.length-1],c=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,t,o,i,v,z=function(a,c){c.parentNode.insertBefore(a,c)};if(c&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,c=document.createElement("div");c.innerHTML=h._iconfont_svg_string_3633546,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(a=document.body).firstChild?z(c,a.firstChild):a.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),l()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(o=l,i=h.document,v=!1,s(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){v||(v=!0,o())}function s(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(s,50)}p()}}(window); \ No newline at end of file +window._iconfont_svg_string_3633546='',function(h){var a=(a=document.getElementsByTagName("script"))[a.length-1],c=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,i,t,o,v,z=function(a,c){c.parentNode.insertBefore(a,c)};if(c&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,c=document.createElement("div");c.innerHTML=h._iconfont_svg_string_3633546,(c=c.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",c=c,(a=document.body).firstChild?z(c,a.firstChild):a.appendChild(c))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),l()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(t=l,o=h.document,v=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,p())})}function p(){v||(v=!0,t())}function s(){try{o.documentElement.doScroll("left")}catch(a){return void setTimeout(s,50)}p()}}(window); \ No newline at end of file diff --git a/chat2db-client/src/assets/font/iconfont.json b/chat2db-client/src/assets/font/iconfont.json index d422aa843..baa53d4e9 100644 --- a/chat2db-client/src/assets/font/iconfont.json +++ b/chat2db-client/src/assets/font/iconfont.json @@ -5,6 +5,97 @@ "css_prefix_text": "icon-", "description": "", "glyphs": [ + { + "icon_id": "5729483", + "name": "database", + "font_class": "database", + "unicode": "e669", + "unicode_decimal": 58985 + }, + { + "icon_id": "2076240", + "name": "筛选", + "font_class": "shaixuan1", + "unicode": "e888", + "unicode_decimal": 59528 + }, + { + "icon_id": "2787037", + "name": "刷新", + "font_class": "shuaxin2", + "unicode": "e668", + "unicode_decimal": 58984 + }, + { + "icon_id": "5387866", + "name": "加号_o", + "font_class": "jiahao_o", + "unicode": "eb78", + "unicode_decimal": 60280 + }, + { + "icon_id": "10594610", + "name": "数据库_jurassic", + "font_class": "jurassic_data", + "unicode": "e6a9", + "unicode_decimal": 59049 + }, + { + "icon_id": "12515461", + "name": "权限", + "font_class": "quanxian", + "unicode": "e667", + "unicode_decimal": 58983 + }, + { + "icon_id": "10573069", + "name": "sharpicons_add-database", + "font_class": "sharpicons_add-database", + "unicode": "e816", + "unicode_decimal": 59414 + }, + { + "icon_id": "6789097", + "name": "组织管理", + "font_class": "zuzhiguanli-", + "unicode": "e663", + "unicode_decimal": 58979 + }, + { + "icon_id": "15568801", + "name": "空间", + "font_class": "moxing-miaobian", + "unicode": "e691", + "unicode_decimal": 59025 + }, + { + "icon_id": "145699", + "name": "电话_填充", + "font_class": "dianhuatianchong", + "unicode": "e678", + "unicode_decimal": 59000 + }, + { + "icon_id": "3483765", + "name": "邮箱", + "font_class": "youxiang", + "unicode": "e672", + "unicode_decimal": 58994 + }, + { + "icon_id": "15933094", + "name": "微信", + "font_class": "weixin", + "unicode": "e65d", + "unicode_decimal": 58973 + }, + { + "icon_id": "19447816", + "name": "谷歌", + "font_class": "google", + "unicode": "eaa8", + "unicode_decimal": 60072 + }, { "icon_id": "37968169", "name": "下箭头-copy", diff --git a/chat2db-client/src/assets/font/iconfont.ttf b/chat2db-client/src/assets/font/iconfont.ttf index d488c9c4cd3b1bdbcd9fc08335c86dbc9b92d6f5..09340b60af2e33856d6e899b9697b8d062bff4d1 100644 GIT binary patch delta 7390 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 J*1XQ>e*nAAq5J>< delta 3501 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%0Dt)zG|Ej7t8hGM5E(P$2^y%RRcCFt?qZ)j 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 f&|attbaBp*T004?K000HJ000S!rkHoK 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 hT@I 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 diff --git a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less index 68b527f18..8b43d91ad 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less +++ b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.less @@ -17,6 +17,10 @@ .consoleOptionsLeft { display: flex; align-items: center; + margin: 0px -6px; + button { + margin: 0px 6px; + } } .runButton { diff --git a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx index 056c8d0b1..03ab4d408 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx @@ -50,11 +50,13 @@ const OperationLine = (props: IProps) => { {i18n('common.button.save')} )} - + +

+
+ {/* */}
-
); }; diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.less b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.less new file mode 100644 index 000000000..4d0949d99 --- /dev/null +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.less @@ -0,0 +1,3 @@ +.consoleOptionsRight{ + display: flex; +} \ No newline at end of file diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx index 132b2e860..5e19602fe 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx @@ -1,13 +1,14 @@ import React, { useEffect, useMemo, useState } from 'react'; import { IBoundInfo } from '../../index'; -import { Select } from 'antd'; +import { Dropdown } from 'antd'; import { useConnectionStore } from '@/store/connection'; import connectionService from '@/service/connection'; +import styles from './index.less'; import { - registerIntelliSenseField, + // registerIntelliSenseField, registerIntelliSenseKeyword, - registerIntelliSenseTable, + // registerIntelliSenseTable, registerIntelliSenseDatabase, } from '@/utils/IntelliSense'; @@ -16,28 +17,31 @@ interface IProps { setBoundInfo: (params: IBoundInfo) => void; } -interface IOption { +interface IOption { + key: string; label: string; - value: any; + value: T; } const SelectBoundInfo = (props: IProps) => { const { boundInfo, setBoundInfo } = props; const connectionList = useConnectionStore((state) => state.connectionList); - const [databaseNameList, setDatabaseNameList] = useState(); - const [schemaList, setSchemaList] = useState(); + const [databaseNameList, setDatabaseNameList] = useState[]>(); + const [schemaList, setSchemaList] = useState[]>(); const dataSourceList = useMemo(() => { return connectionList?.map((item) => ({ + key: item.id.toString(), label: item.alias, value: item.id, + type: item.type, })); }, [connectionList]); // 编辑器绑定的数据库类型变化时,重新注册智能提示 useEffect(() => { registerIntelliSenseKeyword(boundInfo.type); - }, [boundInfo.type]); + }, [boundInfo.dataSourceId]); // 编辑器绑定的数据库类型变化时,重新注册智能提示 useEffect(() => {}, [boundInfo.dataSourceId]); @@ -58,6 +62,7 @@ const SelectBoundInfo = (props: IProps) => { dataSourceName: boundInfo.dataSourceName, }); return { + key: item.name, label: item.name, value: item.name, }; @@ -76,12 +81,13 @@ const SelectBoundInfo = (props: IProps) => { } connectionService .getSchemaList({ - dataSourceId: boundInfo.dataSourceId, + dataSourceId: boundInfo.dataSourceId!, databaseName: boundInfo?.databaseName, }) .then((res: any) => { setSchemaList( res.map((item) => ({ + key: item.name, label: item.name, value: item.name, })), @@ -93,41 +99,65 @@ const SelectBoundInfo = (props: IProps) => { getSchemaList(); }, [boundInfo.databaseName]); + const changeDataSource = (item) => { + const curData = dataSourceList?.find((i) => i.key === item.key); + setBoundInfo({ + ...boundInfo, + dataSourceId: curData?.value, + dataSourceName: curData?.label, + type: curData?.type, + databaseName: void 0, + schemaName: void 0, + }); + }; + + const changeDataBase = (item) => { + const _databaseName = databaseNameList?.find((i) => i.key === item.key)?.value; + + setBoundInfo({ + ...boundInfo, + databaseName: _databaseName, + }); + }; + + const changeSchema = (item) => { + const _schemaName = schemaList?.find((i) => i.key === item.key)?.value; + setBoundInfo({ + ...boundInfo, + schemaName: _schemaName, + }); + }; + return ( -
- { - setBoundInfo({ - ...boundInfo, - databaseName: value, - }); + + > +
{boundInfo.databaseName || '请选择数据库'}
+
)} {boundInfo.databaseName && !!schemaList?.length && ( - } - onBlur={onBlur} - onChange={(e) => { - setSearchKey(e.target.value); - handleValue(); - }} - allowClear - /> -
- ) : ( -
- - -
- {curType.label} - -
-
-
-
-
refreshTableList()}> - -
-
openSearch()}> - -
- {curType.value === TreeNodeType.TABLES && ( - -
- -
-
- )} -
-
- )} -
- -
- - {curType.value === TreeNodeType.TABLES && !curList.length ? ( -
-
{i18n('common.text.noTableFoundUp')}
-
{i18n('common.text.noTableFoundDown')}
-
- ) : ( - - )} -
-
- - {pagingData?.total > 100 && !searchKey && curType.value === TreeNodeType.TABLES && ( -
-
- -
-
- { - setValue(e.target.value); - }} - className={styles.searchHeaderInput} - placeholder={i18n('common.text.search')} - bordered={false} - onPressEnter={() => { - props.handleSearch(value); - }} - /> -
- ); -}); - -export default connect( - ({ - connection, - workspace, - mainPage, - }: { - connection: IConnectionModelType; - workspace: IWorkspaceModelType; - mainPage: IMainPageType; - }) => ({ - connectionModel: connection, - workspaceModel: workspace, - mainPageModel: mainPage, - }), -)(WorkspaceHeader); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx index 54291a8e9..0af4f45df 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx @@ -1,5 +1,5 @@ import React, { memo, useMemo } from 'react'; -import { Dropdown, Tag } from 'antd'; +import { Dropdown } from 'antd'; import classnames from 'classnames'; import styles from './index.less'; @@ -37,10 +37,10 @@ export default memo((props) => { const renderConnectionLabel = (item: IConnectionListItem) => { return (
- + {/* {item.environment.shortName} - - {/* */} + */} +
diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx index a4ebae088..9448d4e39 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx @@ -8,19 +8,22 @@ import { WorkspaceTabType, workspaceTabConfig } from '@/constants'; import { IWorkspaceTab } from '@/typings'; // import WorkspaceExtend from '../WorkspaceExtend'; -// ---- function ----- -import createConsole from '../../functions/createConsole'; - // ----- components ----- import Tabs, { ITabItem } from '@/components/Tabs'; import SearchResult from '@/components/SearchResult'; import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; import SQLExecute from '../SQLExecute'; +import ViewAllTable from '../ViewAllTable'; import Iconfont from '@/components/Iconfont'; import ShortcutKey from '@/components/ShortcutKey'; // ---- store ----- -import { getSavedConsoleList, setActiveConsoleId, setWorkspaceTabList } from '@/pages/main/workspace/store/console'; +import { + getSavedConsoleList, + setActiveConsoleId, + setWorkspaceTabList, + createConsole, +} from '@/pages/main/workspace/store/console'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; // ----- services ----- @@ -59,6 +62,7 @@ const WorkspaceTabs = memo(() => { databaseName: item.databaseName, schemaName: item.schemaName, status: item.status, + ddl: item.ddl, }, }; }) || []; @@ -78,11 +82,13 @@ const WorkspaceTabs = memo(() => { }; const createNewConsole = () => { - createConsole({ - dataSourceId: currentConnectionDetails?.id, - dataSourceName: currentConnectionDetails?.alias, - type: currentConnectionDetails?.type, - }); + if (currentConnectionDetails) { + createConsole({ + dataSourceId: currentConnectionDetails.id, + dataSourceName: currentConnectionDetails.alias, + databaseType: currentConnectionDetails.type, + }); + } }; // 删除 新增tab @@ -174,6 +180,12 @@ const WorkspaceTabs = memo(() => { return ; }; + // 渲染所有表 + const renderViewAllTable = (item: IWorkspaceTab) => { + const { uniqueData } = item; + return ; + }; + // 根据不同的tab类型渲染不同的内容 const workspaceTabConnectionMap = (item: IWorkspaceTab) => { switch (item.type) { @@ -188,6 +200,8 @@ const WorkspaceTabs = memo(() => { return renderTableEditor(item); case WorkspaceTabType.EditTableData: return renderSearchResult(item); + case WorkspaceTabType.ViewAllTable: + return renderViewAllTable(item); default: return
未知类型
; } diff --git a/chat2db-client/src/pages/main/workspace/functions/createConsole.ts b/chat2db-client/src/pages/main/workspace/functions/createConsole.ts deleted file mode 100644 index 6e6fda1e8..000000000 --- a/chat2db-client/src/pages/main/workspace/functions/createConsole.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { setWorkspaceTabList, setActiveConsoleId } from '@/pages/main/workspace/store/console'; -import { useWorkspaceStore } from '@/pages/main/workspace/store'; -import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType, DatabaseTypeCode } from '@/constants' -import historyService from '@/service/history'; - -interface ICreateConsoleParams { - name?: string; - ddl?: string; - dataSourceId?: number; - dataSourceName?: string; - databaseName?: string; - schemaName?: string; - type?: DatabaseTypeCode; - operationType?: string; -} - -const createConsole = (params: ICreateConsoleParams) => { - const workspaceTabList = useWorkspaceStore.getState().workspaceTabList; - - const newConsole = { - ...params, - name: params.name || 'new console', - ddl: params.ddl || '', - status: ConsoleStatus.DRAFT, - tabOpened: ConsoleOpenedStatus.IS_OPEN, - operationType: WorkspaceTabType.CONSOLE, - dataSourceId: params.dataSourceId, - dataSourceName: params.dataSourceName, - }; - - historyService.createConsole(newConsole).then((res) => { - const newList = [ - ...(workspaceTabList||[]), - { - id: res, - title: newConsole.name, - type: newConsole.operationType, - uniqueData: { - ...newConsole, - databaseType: newConsole.type, - }, - }, - ]; - setWorkspaceTabList(newList); - setActiveConsoleId(res); - }); -} - -export default createConsole; diff --git a/chat2db-client/src/pages/main/workspace/functions/shortcutKeyCreateConsole.ts b/chat2db-client/src/pages/main/workspace/functions/shortcutKeyCreateConsole.ts index 900bddbff..668c5824a 100644 --- a/chat2db-client/src/pages/main/workspace/functions/shortcutKeyCreateConsole.ts +++ b/chat2db-client/src/pages/main/workspace/functions/shortcutKeyCreateConsole.ts @@ -1,21 +1,21 @@ -import createConsole from './createConsole'; +import { createConsole} from '../store/console' import { useWorkspaceStore } from '../store'; -import { WorkspaceTabType } from '@/constants'; export const handelCreateConsole = () => { const params = useWorkspaceStore.getState().currentConnectionDetails; - createConsole({ - dataSourceId: params?.id, - dataSourceName: params?.alias, - type: params?.type, - operationType: WorkspaceTabType.CONSOLE - }); + if (params) { + createConsole({ + dataSourceId: params.id, + dataSourceName: params.alias, + databaseType: params.type, + }); + } } const shortcutKeyCreateConsole = () => { // 注册快捷键监听cmd+shift+l或ctrl+shift+l新建一个console const handleKeyDown = (e: KeyboardEvent) => { - if ((e.metaKey && e.shiftKey && e.key === 'L') || (e.ctrlKey && e.shiftKey && e.key === 'L')) { + if ((e.metaKey && e.shiftKey && e.code === 'KeyL') || (e.ctrlKey && e.shiftKey && e.key === 'KeyL')) { handelCreateConsole() } }; diff --git a/chat2db-client/src/pages/main/workspace/store/console.ts b/chat2db-client/src/pages/main/workspace/store/console.ts index 523b4882e..344c32aae 100644 --- a/chat2db-client/src/pages/main/workspace/store/console.ts +++ b/chat2db-client/src/pages/main/workspace/store/console.ts @@ -45,7 +45,11 @@ export const createConsole = (params: ICreateConsoleParams)=>{ type: params.databaseType }; - return new Promise((resolve) => { + return new Promise((resolve, j) => { + if ((workspaceTabList?.length || 0) > 20) { + j('tab数量超过上限'); + return + } historyService.createConsole(newConsole).then((res) => { const newList = [ ...(workspaceTabList||[]), diff --git a/chat2db-client/src/utils/IntelliSense/database.ts b/chat2db-client/src/utils/IntelliSense/database.ts index 915f4bad9..ddb251dff 100644 --- a/chat2db-client/src/utils/IntelliSense/database.ts +++ b/chat2db-client/src/utils/IntelliSense/database.ts @@ -23,7 +23,7 @@ const checkTableContext = (text) => { const registerIntelliSenseDatabase = (databaseName: Array<{ name: string; dataSourceName: string }>) => { intelliSenseDatabase.dispose(); intelliSenseDatabase = monaco.languages.registerCompletionItemProvider('sql', { - triggerCharacters: [' '], + triggerCharacters: [' ', '.'], provideCompletionItems: (model, position) => { const lineContentUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, From 886ec8a4fca96016a1115b2e672a8be9eca30ccf Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 29 Nov 2023 23:18:08 +0800 Subject: [PATCH 103/126] Repeated request --- .../components/SelectBoundInfo/index.tsx | 9 +++ .../src/pages/main/workspace/store/console.ts | 67 ++++++++++++------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx index 71eb981ca..214ad2c4d 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx @@ -67,6 +67,9 @@ const SelectBoundInfo = memo((props: IProps) => { // 当数据源变化时,重新获取数据库列表 useEffect(() => { + if (!isActive) { + return; + } if (supportDatabase) { setSchemaList([]); setDatabaseNameList([]); @@ -79,6 +82,9 @@ const SelectBoundInfo = memo((props: IProps) => { // 当数据库名变化时,重新获取schema列表 useEffect(() => { + if (!isActive) { + return; + } if (supportSchema) { getSchemaList(); } @@ -88,6 +94,9 @@ const SelectBoundInfo = memo((props: IProps) => { }, [boundInfo.databaseName]); useEffect(() => { + if (!isActive) { + return; + } if (supportSchema && boundInfo.schemaName) { getAllTableNameList(boundInfo.dataSourceId, boundInfo.databaseName, boundInfo.schemaName); } diff --git a/chat2db-client/src/pages/main/workspace/store/console.ts b/chat2db-client/src/pages/main/workspace/store/console.ts index 344c32aae..ae7954068 100644 --- a/chat2db-client/src/pages/main/workspace/store/console.ts +++ b/chat2db-client/src/pages/main/workspace/store/console.ts @@ -8,33 +8,37 @@ export interface IConsoleStore { consoleList: IConsole[] | null; activeConsoleId: string | number | null; workspaceTabList: IWorkspaceTab[] | null; + createConsoleLoading: boolean } export const initConsoleStore = { consoleList: null, activeConsoleId: null, workspaceTabList: null, -} + createConsoleLoading: false +}; export const getSavedConsoleList = () => { - historyService.getSavedConsoleList({ - tabOpened: 'y', - pageNo: 1, - pageSize: 20, - }).then((res) => { - useWorkspaceStore.setState({ consoleList: res?.data }); - }); -} + historyService + .getSavedConsoleList({ + tabOpened: 'y', + pageNo: 1, + pageSize: 20, + }) + .then((res) => { + useWorkspaceStore.setState({ consoleList: res?.data }); + }); +}; export const setActiveConsoleId = (id: IConsoleStore['activeConsoleId']) => { useWorkspaceStore.setState({ activeConsoleId: id }); -} +}; export const setWorkspaceTabList = (items: IConsoleStore['workspaceTabList']) => { useWorkspaceStore.setState({ workspaceTabList: items }); -} +}; -export const createConsole = (params: ICreateConsoleParams)=>{ +export const createConsole = (params: ICreateConsoleParams) => { const workspaceTabList = useWorkspaceStore.getState().workspaceTabList; const newConsole = { ...params, @@ -42,17 +46,31 @@ export const createConsole = (params: ICreateConsoleParams)=>{ ddl: params.ddl || '', status: ConsoleStatus.DRAFT, operationType: WorkspaceTabType.CONSOLE, - type: params.databaseType + type: params.databaseType, }; - return new Promise((resolve, j) => { + return new Promise((resolve) => { if ((workspaceTabList?.length || 0) > 20) { - j('tab数量超过上限'); - return + return; } + useWorkspaceStore.setState({ createConsoleLoading: true }); historyService.createConsole(newConsole).then((res) => { + // 找到活跃的id的位置 + // const activeIndex = workspaceTabList?.findIndex( + // (item) => item?.id === useWorkspaceStore.getState().activeConsoleId, + // ); + // // 向活跃的位置后插入数据 + // if (activeIndex !== -1) { + // workspaceTabList?.splice(activeIndex + 1, 0, { + // id: res, + // title: newConsole.name, + // type: newConsole.operationType, + // uniqueData: newConsole, + // }); + // } + const newList = [ - ...(workspaceTabList||[]), + ...(workspaceTabList || []), { id: res, title: newConsole.name, @@ -60,24 +78,25 @@ export const createConsole = (params: ICreateConsoleParams)=>{ uniqueData: newConsole, }, ]; + setWorkspaceTabList(newList); setActiveConsoleId(res); resolve(res); + }) + .finally(() => { + useWorkspaceStore.setState({ createConsoleLoading: false }); }); }); -} +}; export const addWorkspaceTab = (params: IWorkspaceTab) => { const workspaceTabList = useWorkspaceStore.getState().workspaceTabList; - if(workspaceTabList?.findIndex((item) => item?.id === params?.id) !== -1){ + if (workspaceTabList?.findIndex((item) => item?.id === params?.id) !== -1) { setActiveConsoleId(params.id); return; } - - const newList = [ - ...(workspaceTabList||[]), - params, - ]; + + const newList = [...(workspaceTabList || []), params]; setWorkspaceTabList(newList); setActiveConsoleId(params.id); From 56211a37ed0b08c4f3d5f16d2a17dc9374d5cd0d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 30 Nov 2023 14:57:16 +0800 Subject: [PATCH 104/126] fix:Dashboard --- chat2db-client/.vscode/settings.json | 1 + chat2db-client/src/blocks/Tree/index.tsx | 35 ++++++-- chat2db-client/src/blocks/Tree/treeConfig.tsx | 15 ++-- .../components/SelectBoundInfo/index.tsx | 34 +++++--- .../ConsoleEditor/hooks/useSaveEditorData.ts | 2 +- .../src/components/ConsoleEditor/index.tsx | 4 +- .../main/dashboard/chart-item/index.less | 23 ++--- .../pages/main/dashboard/chart-item/index.tsx | 85 ++++++++----------- .../pages/main/dashboard/chart/pie/index.tsx | 10 +-- .../src/pages/main/dashboard/index.tsx | 66 ++++++-------- chat2db-client/src/typings/tree.ts | 4 + 11 files changed, 137 insertions(+), 142 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 1606fa2a6..0354c8cbd 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -36,6 +36,7 @@ "DBAI", "dbhub", "Dmaven", + "echart", "echarts", "favicons", "findstr", diff --git a/chat2db-client/src/blocks/Tree/index.tsx b/chat2db-client/src/blocks/Tree/index.tsx index a5543e536..e870a2701 100644 --- a/chat2db-client/src/blocks/Tree/index.tsx +++ b/chat2db-client/src/blocks/Tree/index.tsx @@ -85,13 +85,22 @@ const TreeNode = memo((props: TreeNodeIProps) => { }, refresh: _props?.refresh || false, }) - .then((res) => { - if (res.length) { + .then((res: any) => { + if (res.length || res.data) { setTimeout(() => { - setTreeNodeData({ - ...treeNodeData, - children: res, - }); + console.log(res); + if (res.data) { + setTreeNodeData({ + ...treeNodeData, + children: res.data, + total: res.total, + }); + } else { + setTreeNodeData({ + ...treeNodeData, + children: res, + }); + } setIsLoading(false); }, 200); } else { @@ -274,11 +283,21 @@ const TreeNode = memo((props: TreeNodeIProps) => { ); }, [isFocus, isLoading, rightClickMenu]); + const sectionHeight = useMemo(() => { + console.log(treeNodeData.total); + + if (treeNodeData.total) { + return `${treeNodeData.total * 26}px`; + } else { + return 'auto'; + } + }, [treeNodeData.total]); + return ( - <> +
{(showTreeNode || showParentNode) && treeNodeDom} {treeNodes} - +
); }); diff --git a/chat2db-client/src/blocks/Tree/treeConfig.tsx b/chat2db-client/src/blocks/Tree/treeConfig.tsx index 060147495..e7bb89fe2 100644 --- a/chat2db-client/src/blocks/Tree/treeConfig.tsx +++ b/chat2db-client/src/blocks/Tree/treeConfig.tsx @@ -261,13 +261,12 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, }; }); - r(tableList); - // { - // data: tableList, - // pageNo: res.pageNo, - // pageSize: res.pageSize, - // total: res.total, - // } + r({ + data: tableList, + pageNo: res.pageNo, + pageSize: res.pageSize, + total: res.total, + } as any); }) .catch((error) => { j(error); @@ -276,7 +275,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, operationColumn: [ OperationColumn.CreateConsole, - OperationColumn.ViewAllTable, + // OperationColumn.ViewAllTable, OperationColumn.CreateTable, OperationColumn.Refresh, ], diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx index 214ad2c4d..1428f0c29 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx @@ -190,12 +190,14 @@ const SelectBoundInfo = memo((props: IProps) => { databaseName: void 0, schemaName: void 0, }); - historyService.updateSavedConsole({ - id: boundInfo.consoleId, - dataSourceId: currentData.value, - dataSourceName: currentData.label, - type: currentData.type, - }); + if (boundInfo.consoleId) { + historyService.updateSavedConsole({ + id: boundInfo.consoleId, + dataSourceId: currentData.value, + dataSourceName: currentData.label, + type: currentData.type, + }); + } }; // 选择数据库 @@ -208,10 +210,12 @@ const SelectBoundInfo = memo((props: IProps) => { schemaName: void 0, }); - historyService.updateSavedConsole({ - id: boundInfo.consoleId, - databaseName: _databaseName, - }); + if (boundInfo.consoleId) { + historyService.updateSavedConsole({ + id: boundInfo.consoleId, + databaseName: _databaseName, + }); + } }; // 选择schema @@ -222,10 +226,12 @@ const SelectBoundInfo = memo((props: IProps) => { schemaName: _schemaName, }); - historyService.updateSavedConsole({ - id: boundInfo.consoleId, - schemaName: _schemaName, - }); + if (boundInfo.consoleId) { + historyService.updateSavedConsole({ + id: boundInfo.consoleId, + schemaName: _schemaName, + }); + } }; const getAllTableNameList = (dataSourceId, databaseName, schemaName?) => { diff --git a/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts b/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts index 55abcc101..d9cb55328 100644 --- a/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts +++ b/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts @@ -9,7 +9,7 @@ import { getCookie } from '@/utils'; interface IProps { isActive?: boolean; - source: string; + source?: string; editorRef: any; boundInfo: any; defaultValue?: string; diff --git a/chat2db-client/src/components/ConsoleEditor/index.tsx b/chat2db-client/src/components/ConsoleEditor/index.tsx index 48d5705f3..08c7e1954 100644 --- a/chat2db-client/src/components/ConsoleEditor/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/index.tsx @@ -48,7 +48,7 @@ export type IAppendValue = { }; export interface IBoundInfo { - consoleId: number; + consoleId?: number; dataSourceId: number; dataSourceName: string; databaseType: DatabaseTypeCode; @@ -59,7 +59,7 @@ export interface IBoundInfo { interface IProps { /** 调用来源 */ - source: 'workspace'; + source?: 'workspace'; isActive: boolean; /** 添加或修改的内容 */ appendValue?: IAppendValue; diff --git a/chat2db-client/src/pages/main/dashboard/chart-item/index.less b/chat2db-client/src/pages/main/dashboard/chart-item/index.less index 2a8b1d643..2307f4736 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.less +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.less @@ -1,7 +1,6 @@ .container { flex: 1; position: relative; - // border: 1px solid var(--color-gray-300); border: 1px solid var(--color-border); border-radius: var(--border-radius-l-g); padding: 16px; @@ -16,9 +15,6 @@ font-size: 14px; } -.title { -} - .edit { cursor: pointer; } @@ -117,24 +113,26 @@ } .editBlock { - border-radius: var(--border-radius-l-g); - background-color: var(--color-bg-subtle); - max-height: 600px; + border-radius: 4px; + // background-color: var(--color-bg-subtle); + max-height: 1000px; } .editorBlock { display: flex; - border-bottom: 1px solid var(--color-border-secondary); + // flex-direction: column; flex-wrap: wrap; } .editor { flex: 2; min-width: 320px; - max-height: 320px; + min-height: 320px; display: flex; flex-direction: column; position: relative; + border: 1px solid var(--color-border-secondary); + overflow: hidden; } .dataSourceSelect { @@ -144,10 +142,11 @@ } .chartParamsForm { + border: 1px solid var(--color-border); + border-left: 1px solid var(--color-border); color: var(--color-text); flex: 1; min-width: 120px; - border-left: 1px solid var(--color-border-secondary); display: flex; flex-direction: column; justify-content: start; @@ -163,5 +162,7 @@ } .editorOptionBlock { - padding: 12px; + display: flex; + justify-content: flex-end; + padding: 20px 0px 0px 0px; } diff --git a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx index 2dc4bb59e..4dff1dd41 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -1,4 +1,4 @@ -import { IChartItem, IChartType, IConnectionDetails, ITreeNode } from '@/typings'; +import { IChartItem, IChartType, IConnectionDetails } from '@/typings'; import React, { useEffect, useRef, useState, useMemo } from 'react'; import styles from './index.less'; import addImage from '@/assets/img/add.svg'; @@ -7,7 +7,7 @@ import Line from '../chart/line'; import Pie from '../chart/pie'; import Bar from '../chart/bar'; import { MoreOutlined } from '@ant-design/icons'; -import { Button, Cascader, Dropdown, Form, message, Select, Spin } from 'antd'; +import { Button, Dropdown, Form, message, Select, Spin } from 'antd'; import { deleteChart, getChartById, updateChart } from '@/service/dashboard'; import ConsoleEditor from '@/components/ConsoleEditor'; import Iconfont from '@/components/Iconfont'; @@ -15,9 +15,6 @@ import sqlService, { IExecuteSqlParams } from '@/service/sql'; import { Option } from '@/typings/common'; import { handleDatabaseAndSchema } from '@/utils/database'; import i18n from '@/i18n'; -import { useTheme } from '@/hooks'; -import { EditorThemeType, ThemeType } from '@/constants'; -import { IRemainingUse } from '@/typings/ai'; import { isValid } from '@/utils/check'; const handleSQLResult2ChartData = (data) => { @@ -54,9 +51,8 @@ interface IChartItemProps { id: number; isEditing?: boolean; canAddRowItem: boolean; - connectionList: IConnectionDetails[]; - tableList: ITreeNode[]; - remainingUse: IRemainingUse; + connectionList: any[]; + remainingUse: any; onDelete?: (id: number) => void; addChartTop?: () => void; addChartBottom?: () => void; @@ -65,20 +61,17 @@ interface IChartItemProps { } function ChartItem(props: IChartItemProps) { - const { connectionList, tableList, remainingUse } = props; + const { connectionList, id } = props; const [cascaderOption, setCascaderOption] = useState([]); const [curConnection, setCurConnection] = useState(); const [chartData, setChartData] = useState({}); const [chartMetaData, setChartMetaData] = useState(); - const [cascaderValue, setCascaderValue] = useState<(string | number)[]>([]); + const [, setCascaderValue] = useState<(string | number)[]>([]); const [isEditing, setIsEditing] = useState(props.isEditing ?? false); const [isLoading, setIsLoading] = useState(false); const [initDDL, setInitDDL] = useState(''); const [form] = Form.useForm(); // 创建一个表单实例 const chartRef = useRef(); - const [appTheme] = useTheme(); - - const { id } = props; useEffect(() => { if (id !== undefined) { @@ -116,13 +109,6 @@ function ChartItem(props: IChartItemProps) { handleChartConfigChange(); }, [chartData.sqlData]); - const loadData = (selectedOptions: any) => { - // 只选择了dataSource - const dataSourceId = selectedOptions[0].value; - const dataSource = connectionList.find((c) => c.id === dataSourceId); - setCurConnection(dataSource); - }; - const queryDatabaseAndSchemaList = async (dataSourceId: number) => { const res = await sqlService.getDatabaseSchemaList({ dataSourceId }); const dataSource = (cascaderOption || []).find((c) => c.value === dataSourceId); @@ -132,28 +118,28 @@ function ChartItem(props: IChartItemProps) { setCascaderOption([...cascaderOption]); }; - const handleExecuteSQL = async (sql: string, chartData: IChartItem) => { - const { dataSourceId, databaseName } = chartData; + const handleExecuteSQL = async (sql: string, _chartData: IChartItem) => { + const { dataSourceId, databaseName } = _chartData; if (!isValid(dataSourceId)) { message.success(i18n('dashboard.editor.execute.noDataSource')); return; } setIsLoading(true); try { - let executeSQLParams: IExecuteSqlParams = { + const executeSQLParams: IExecuteSqlParams = { sql, dataSourceId, databaseName, }; // 获取当前SQL的查询结果 - let sqlResult = await sqlService.executeSql(executeSQLParams); + const sqlResult = await sqlService.executeSql(executeSQLParams); let sqlData; if (sqlResult && sqlResult[0]) { sqlData = handleSQLResult2ChartData(sqlResult[0]); } setChartData({ - ...chartData, + ..._chartData, ddl: sql, sqlData, }); @@ -167,14 +153,13 @@ function ChartItem(props: IChartItemProps) { const queryChartData = async () => { setIsLoading(true); - const { id } = props; - let res = await getChartById({ id }); + const res = await getChartById({ id: props.id }); setChartData(res); // 设置级联value const cascaderKey = ['dataSourceId', 'databaseName', 'schemaName']; - const cascaderValue = cascaderKey.map((k: string) => res[k]).filter((i) => !!i); - setCascaderValue(cascaderValue); + const _cascaderValue = cascaderKey.map((k: string) => res[k]).filter((i) => !!i); + setCascaderValue(_cascaderValue); // 设置Chart参数,eg ChartType、xAxis、yAxis const formValue = JSON.parse(res.schema || '{}'); @@ -206,22 +191,22 @@ function ChartItem(props: IChartItemProps) { const onExport2Image = () => { const echartInstance = chartRef.current.getEchartsInstance(); - let img = new Image(); + const img = new Image(); img.src = echartInstance.getDataURL({ type: 'png', devicePixelRatio: 4, backgroundColor: '#FFF', }); img.onload = function () { - let canvas = document.createElement('canvas'); + const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; - let ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d'); ctx?.drawImage(img, 0, 0); - let dataURL = canvas.toDataURL('image/png'); + const dataURL = canvas.toDataURL('image/png'); - var a = document.createElement('a'); - let event = new MouseEvent('click'); + const a = document.createElement('a'); + const event = new MouseEvent('click'); a.download = 'image.png'; a.href = dataURL; a.dispatchEvent(event); @@ -229,9 +214,8 @@ function ChartItem(props: IChartItemProps) { }; const onDeleteChart = () => { - const { id } = props; - deleteChart({ id }); - props.onDelete && props.onDelete(id); + deleteChart({ id: props.id }); + props.onDelete && props.onDelete(props.id); }; const handleSaveChart = async () => { @@ -270,7 +254,6 @@ function ChartItem(props: IChartItemProps) { xAxis: dimensionX, yAxis: dimensionY, }); - } else { } }; @@ -350,6 +333,13 @@ function ChartItem(props: IChartItemProps) { }; }, [initDDL]); + const setBoundInfo = (boundInfo) => { + setChartData({ + ...chartData, + ...boundInfo, + }); + }; + const renderEditorBlock = () => { const { sqlData = {} } = chartData || {}; const options = Object.keys(sqlData).map((i) => ({ label: i, value: i })); @@ -359,8 +349,9 @@ function ChartItem(props: IChartItemProps) {
handleExecuteSQL(sql, chartData)} editorOptions={{ lineNumbers: 'off', - theme: - appTheme.backgroundColor === ThemeType.Light - ? EditorThemeType.DashboardLightTheme - : EditorThemeType.DashboardBlackTheme, }} + isActive={true} /> - - { + (selectedOptions || []).forEach((o: any) => { if (o.type) { p[`${o.type}Name`] = o.value; } else { @@ -400,7 +387,7 @@ function ChartItem(props: IChartItemProps) { className={styles.dataSourceSelect} placeholder={i18n('dashboard.editor.cascader.placeholder')} // style={{ width: '100%' }} - /> + /> */}
Charts:
diff --git a/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx b/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx index 3b9c1368b..2ed789e55 100644 --- a/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx @@ -1,9 +1,7 @@ -import React, { ForwardedRef, LegacyRef, useImperativeHandle, useMemo, useRef } from 'react'; +import React, { ForwardedRef, forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import * as charts from 'echarts'; import ReactEcharts from 'echarts-for-react'; import './index.less'; -import { Button } from 'antd'; -import { forwardRef } from 'react'; type EChartsOption = charts.EChartsOption; interface IProps { @@ -24,7 +22,6 @@ const PieChart = (props: IProps, ref: ForwardedRef<{ getEchartsInstance: Functio type: 'scroll', //分页类型 }, - series: [ { type: 'pie', @@ -38,12 +35,11 @@ const PieChart = (props: IProps, ref: ForwardedRef<{ getEchartsInstance: Functio label: { show: true, fontSize: 16, - fontWeight: 'bold' - } + fontWeight: 'bold', + }, }, }, ], - }), [props.data], ); diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index 3ecd84530..468d2a5ad 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -1,8 +1,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { Dropdown, Form, Input, Modal, message } from 'antd'; -import { connect, Dispatch } from 'umi'; import cs from 'classnames'; -import { IChartItem, IConnectionDetails, IDashboardItem, ITreeNode } from '@/typings'; +import { IChartItem, IDashboardItem } from '@/typings'; import DraggableContainer from '@/components/DraggableContainer'; import Iconfont from '@/components/Iconfont'; import ChartItem from './chart-item'; @@ -16,18 +15,15 @@ import { updateDashboard, } from '@/service/dashboard'; import i18n from '@/i18n'; -import { IConnectionModelState } from '@/models/connection'; import styles from './index.less'; -import { IWorkspaceModelState } from '@/models/workspace'; -import { IAIState } from '@/models/ai'; -import { IRemainingUse } from '@/typings/ai'; +// import { IConnectionModelState } from '@/models/connection'; +// import { IWorkspaceModelState } from '@/models/workspace'; +// import { IAIState } from '@/models/ai'; +import { useConnectionStore } from '../store/connection'; +import { useSettingStore } from '@/store/setting'; interface IProps { className?: string; - connectionList: IConnectionDetails[]; - curTableList: ITreeNode[]; - remainingUse: IRemainingUse; - dispatch: Dispatch; } export const initChartItem: IChartItem = { @@ -38,23 +34,20 @@ export const initChartItem: IChartItem = { }; function Chart(props: IProps) { - const { className, connectionList, curTableList, remainingUse, dispatch } = props; + const { className } = props; const [dashboardList, setDashboardList] = useState([]); const [curDashboard, setCurDashboard] = useState(); const [openAddDashboard, setOpenAddDashboard] = useState(false); + const connectionList = useConnectionStore((state) => state.connectionList); + const remainingUse = useSettingStore((state) => state.remainingUse); const [form] = Form.useForm(); // 创建一个表单实例 - const [messageApi, contextHolder] = message.useMessage(); + const [messageApi] = message.useMessage(); const draggableRef = useRef(); useEffect(() => { // 获取列表数据 queryDashboardList(); - - // 如果没有连接池,触发一次请求 - dispatch({ - type: 'connection/fetchConnectionList', - }); }, []); useEffect(() => { @@ -68,19 +61,19 @@ function Chart(props: IProps) { }, [curDashboard]); const queryDashboardList = async () => { - let res = await getDashboardList({}); + const res = await getDashboardList({}); const { data } = res; if (Array.isArray(data) && data.length > 0) { setDashboardList(data); - let curDashboard = await getDashboardById({ id: data[0].id }); - setCurDashboard(curDashboard); + const _curDashboard = await getDashboardById({ id: data[0].id }); + setCurDashboard(_curDashboard); } }; const initCreateChart = async (dashboard?: IDashboardItem) => { if (!dashboard) return; - let chartId = await createChart({}); + const chartId = await createChart({}); const newDashboard = { ...dashboard, schema: JSON.stringify([[chartId]]), @@ -95,7 +88,7 @@ function Chart(props: IProps) { if (curDashboard?.id === id) { return; } - let res = await getDashboardById({ id }); + const res = await getDashboardById({ id }); setCurDashboard(res); }; @@ -153,7 +146,7 @@ function Chart(props: IProps) { const { id, schema, chartIds = [] } = curDashboard || {}; const chartList: number[][] = JSON.parse(schema || '') || [[]]; - let chartId = await createChart({}); + const chartId = await createChart({}); switch (type) { case 'top': chartList.splice(rowIndex, 0, [chartId]); @@ -195,7 +188,7 @@ function Chart(props: IProps) { id: id!, ...curDashboard, schema: JSON.stringify(chartList), - chartIds: chartIds?.filter((id) => id !== chartId), + chartIds: chartIds?.filter((_id) => _id !== chartId), }; await updateDashboard(newDashboard); setCurDashboard(newDashboard); @@ -218,7 +211,11 @@ function Chart(props: IProps) { {chartList.map((rowData: number[], rowIndex: number) => (
{rowData.map((chartId: number, colIndex: number) => ( -
+
onAddChart('right', rowIndex, colIndex)} onDelete={(id: number) => onDeleteChart(id, rowIndex, colIndex)} connectionList={connectionList || []} - tableList={curTableList || []} remainingUse={remainingUse} />
@@ -261,7 +257,7 @@ function Chart(props: IProps) { open={openAddDashboard} onOk={async () => { try { - const values = await form.validateFields(); + await form.validateFields(); const formValue = form.getFieldsValue(true); const { id } = formValue; @@ -301,18 +297,4 @@ function Chart(props: IProps) { ); } -export default connect( - ({ - connection, - workspace, - ai, - }: { - connection: IConnectionModelState; - workspace: IWorkspaceModelState; - ai: IAIState; - }) => ({ - connectionList: connection.connectionList, - curTableList: workspace.curTableList, - remainingUse: ai.remainingUse, - }), -)(Chart); +export default Chart; diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index 31ddb57ef..945f4784e 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -29,6 +29,10 @@ export interface ITreeNode { loadData?: (params:{refresh: boolean}) => void; // 加载数据的方法 // 父元素 parentNode?: ITreeNode; + // 分页 + page?: number; + pageSize?: number; + total?: number; } // 视图 函数 触发器 过程 通用的返回结果 From 67ae08430aeb5e531957931b9dda818fa4703abf Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 30 Nov 2023 15:19:02 +0800 Subject: [PATCH 105/126] fix:bug --- .../ConsoleEditor/components/SelectBoundInfo/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx index 1428f0c29..79a4e3322 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx @@ -78,7 +78,7 @@ const SelectBoundInfo = memo((props: IProps) => { setSchemaList([]); getSchemaList(); } - }, [boundInfo.dataSourceId]); + }, [boundInfo.dataSourceId, isActive]); // 当数据库名变化时,重新获取schema列表 useEffect(() => { @@ -91,7 +91,7 @@ const SelectBoundInfo = memo((props: IProps) => { if (!supportSchema && boundInfo.databaseName) { getAllTableNameList(boundInfo.dataSourceId, boundInfo.databaseName); } - }, [boundInfo.databaseName]); + }, [boundInfo.databaseName, isActive]); useEffect(() => { if (!isActive) { @@ -100,7 +100,7 @@ const SelectBoundInfo = memo((props: IProps) => { if (supportSchema && boundInfo.schemaName) { getAllTableNameList(boundInfo.dataSourceId, boundInfo.databaseName, boundInfo.schemaName); } - }, [boundInfo.schemaName]); + }, [boundInfo.schemaName, isActive]); // 获取数据库列表 const getDatabaseList = () => { From 44507e1682a7e0fc0d878e9d5c3900da14d318c3 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Thu, 30 Nov 2023 16:01:01 +0800 Subject: [PATCH 106/126] ai deserialization update --- .../controller/ai/azure/client/AzureOpenAiStreamClient.java | 2 ++ .../controller/ai/baichuan/client/BaichuanAIStreamClient.java | 2 ++ .../controller/ai/chat2db/client/Chat2DBAIStreamClient.java | 3 +++ .../api/controller/ai/claude/client/ClaudeAiStreamClient.java | 2 ++ .../controller/ai/fastchat/client/FastChatAIStreamClient.java | 2 ++ .../ai/openai/listener/OpenAIEventSourceListener.java | 2 ++ .../web/api/controller/ai/rest/client/RestAiStreamClient.java | 3 +++ .../controller/ai/tongyi/client/TongyiChatAIStreamClient.java | 2 ++ .../api/controller/ai/wenxin/client/WenxinAIStreamClient.java | 2 ++ .../controller/ai/zhipu/client/ZhipuChatAIStreamClient.java | 2 ++ .../src/main/java/ai/chat2db/spi/util/FileUtils.java | 2 ++ 11 files changed, 24 insertions(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java index e232936b7..338f5b1c1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java @@ -9,6 +9,7 @@ import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -166,6 +167,7 @@ public void streamCompletions(List chatMessages, EventSourceLi EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); if (!endpoint.endsWith("/")) { endpoint = endpoint + "/"; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java index 9cbefc1a1..57aa33bc4 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/baichuan/client/BaichuanAIStreamClient.java @@ -5,6 +5,7 @@ import ai.chat2db.server.web.api.controller.ai.baichuan.model.BaichuanChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -191,6 +192,7 @@ public void streamCompletions(List chatMessages, EventSourceLis chatCompletionsOptions.setMessages(chatMessages); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java index 6f6b639f0..0f0b6d84f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/chat2db/client/Chat2DBAIStreamClient.java @@ -11,6 +11,7 @@ import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.ChatCompletion; import com.unfbx.chatgpt.entity.chat.Message; @@ -204,6 +205,7 @@ public void streamCompletions(List chatMessages, EventSourceListener ev EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletion); Request request = new Request.Builder() .url(this.apiHost + "v1/chat/completions") @@ -242,6 +244,7 @@ public FastChatEmbeddingResponse embeddings(String input) { public FastChatEmbeddingResponse embeddings(FastChatEmbedding embedding) { try { ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(embedding); Request request = new Request.Builder() .url(this.apiHost + "v1/embeddings") diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java index d36079025..692a32a72 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java @@ -4,6 +4,7 @@ import ai.chat2db.server.web.api.controller.ai.claude.interceptor.ClaudeHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -167,6 +168,7 @@ public void streamCompletions(ClaudeChatMessage claudeChatMessage, EventSourceLi claudeChatMessage.setConversation_uuid(this.userId); EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(claudeChatMessage); Request request = new Request.Builder() diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java index fd371d37d..d3dcaadf2 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java @@ -7,6 +7,7 @@ import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import io.reactivex.Single; import lombok.Getter; @@ -199,6 +200,7 @@ public void streamCompletions(List chatMessages, EventSourceLis EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java index 2f0bcef01..ccadf6d68 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java @@ -4,6 +4,7 @@ import ai.chat2db.server.web.api.controller.ai.response.ChatCompletionResponse; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; import lombok.SneakyThrows; @@ -54,6 +55,7 @@ public void onEvent(EventSource eventSource, String id, String type, String data return; } ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 读取Json ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); String text = completionResponse.getChoices().get(0).getDelta() == null diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAiStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAiStreamClient.java index 7e000702d..fba46a4e6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAiStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAiStreamClient.java @@ -7,6 +7,7 @@ import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.rest.model.RestAiCompletion; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.sse.ConsoleEventSourceListener; import lombok.Getter; @@ -102,6 +103,7 @@ public void streamCompletions(RestAiCompletion completion, EventSourceListener e try { EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(completion); Request request = new Request.Builder() .url(this.apiUrl) @@ -128,6 +130,7 @@ public void nonStreamCompletions(RestAiCompletion completion, EventSourceListene } try { ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(completion); Request request = new Request.Builder() .url(this.apiUrl) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java index f2806ed21..36e71614d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/tongyi/client/TongyiChatAIStreamClient.java @@ -6,6 +6,7 @@ import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatCompletionsOptions; import ai.chat2db.server.web.api.controller.ai.tongyi.model.TongyiChatMessage; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -192,6 +193,7 @@ public void streamCompletions(List chatMessages, EventSourceLis EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java index 86b247f86..b89744d60 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/wenxin/client/WenxinAIStreamClient.java @@ -5,6 +5,7 @@ import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.wenxin.interceptor.AccessTokenInterceptor; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -179,6 +180,7 @@ public void streamCompletions(List chatMessages, EventSourceLis EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(chatCompletionsOptions); Request request = new Request.Builder() .url(apiHost) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java index 59034eca6..550c929eb 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/zhipu/client/ZhipuChatAIStreamClient.java @@ -5,6 +5,7 @@ import ai.chat2db.server.web.api.controller.ai.zhipu.interceptor.ZhipuChatHeaderAuthorizationInterceptor; import ai.chat2db.server.web.api.controller.ai.zhipu.model.ZhipuChatCompletionsOptions; import cn.hutool.http.ContentType; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -201,6 +202,7 @@ public void streamCompletions(List chatMessages, EventSourceLis String requestId = String.valueOf(System.currentTimeMillis()); completionsOptions.setRequestId(requestId); ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String requestBody = mapper.writeValueAsString(completionsOptions); String url = this.apiHost + "/" + this.model + "/" + "sse-invoke"; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java index 496640d06..0f04b99ca 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/FileUtils.java @@ -1,6 +1,7 @@ package ai.chat2db.spi.util; import ai.chat2db.spi.config.DBConfig; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; @@ -9,6 +10,7 @@ public class FileUtils { public static T readJsonValue(Class loaderClass, String path, Class clazz) { ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); T value = null; try { value = mapper.readValue(loaderClass.getResourceAsStream(path), clazz); From 3b1c197425ac8c2c3b5addd21e4ab68e032d3e02 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Thu, 30 Nov 2023 17:57:42 +0800 Subject: [PATCH 107/126] operation order desc --- .../api/param/operation/OperationPageQueryParam.java | 7 ++++++- .../server/domain/core/impl/OperationServiceImpl.java | 7 ++++--- .../operation/saved/request/OperationQueryRequest.java | 5 +++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java index 4de4d19a1..d0232b300 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java @@ -40,7 +40,12 @@ public class OperationPageQueryParam extends PageQueryParam { /** * orderBy modify time desc */ - private boolean orderByDesc; + private Boolean orderByDesc; + + /** + * orderBy create time desc + */ + private Boolean orderByCreateDesc; /** * operation type diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java index 135508df4..93652f0de 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationServiceImpl.java @@ -143,10 +143,11 @@ public PageResult queryPage(OperationPageQueryParam param) { Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); page.setOptimizeCountSql(false); - if (param.isOrderByDesc()) { + if (Objects.nonNull(param.getOrderByDesc()) && param.getOrderByDesc()) { queryWrapper.orderByDesc("gmt_modified"); - } else { - queryWrapper.orderByAsc("gmt_modified"); + } + if (Objects.nonNull(param.getOrderByCreateDesc()) && param.getOrderByCreateDesc()) { + queryWrapper.orderByDesc("gmt_create"); } IPage iPage = operationSavedMapper.selectPage(page, queryWrapper); List userSavedDdlDOS = operationConverter.do2dto(iPage.getRecords()); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationQueryRequest.java index 31dd7702c..0723d47aa 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationQueryRequest.java @@ -42,6 +42,11 @@ public class OperationQueryRequest extends PageQueryRequest { */ private Boolean orderByDesc; + /** + * orderBy create time desc + */ + private Boolean orderByCreateDesc; + /** * operation type */ From bc8a0506bf9004a0dae53a1ce9ef5c01ec8892b3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 30 Nov 2023 21:49:29 +0800 Subject: [PATCH 108/126] fix: delete connection bug --- .../src/blocks/DatabaseTableEditor/index.less | 1 + chat2db-client/src/blocks/Setting/index.tsx | 12 ++----- chat2db-client/src/blocks/Tree/index.tsx | 19 +++++----- .../components/SelectBoundInfo/index.tsx | 9 ++--- .../src/components/ConsoleEditor/index.tsx | 13 +------ chat2db-client/src/hooks/getConnection.ts | 15 ++++---- chat2db-client/src/i18n/en-us/workspace.ts | 1 + chat2db-client/src/i18n/zh-cn/workspace.ts | 1 + .../src/pages/main/connection/index.tsx | 4 +-- .../src/pages/main/functions/getConnection.ts | 10 ++++++ chat2db-client/src/pages/main/index.tsx | 12 ++++--- .../src/pages/main/store/connection/index.ts | 29 ++++++++++++++- .../workspace/components/SQLExecute/index.tsx | 14 ++------ .../workspace/components/TableList/index.tsx | 1 + .../components/WorkspaceLeft/index.less | 23 ++++++++++++ .../components/WorkspaceLeft/index.tsx | 35 +++++++++++++++++-- .../components/WorkspaceLeftHeader/index.less | 4 +-- .../components/WorkspaceLeftHeader/index.tsx | 25 ++++++------- .../components/WorkspaceTabs/index.tsx | 3 ++ .../src/pages/main/workspace/store/console.ts | 14 -------- chat2db-client/src/typings/workspace.ts | 13 ++++++- 21 files changed, 162 insertions(+), 96 deletions(-) create mode 100644 chat2db-client/src/pages/main/functions/getConnection.ts diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less index fc2b8ddbe..929c64cde 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -52,6 +52,7 @@ border-radius: 4px; cursor: pointer; font-size: 13px; + user-select: none; &:hover { color: var(--color-primary); } diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index dbaee477f..cc2bb7bb3 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -7,19 +7,16 @@ import BaseSetting from './BaseSetting'; import AISetting from './AiSetting'; import ProxySetting from './ProxySetting'; import About from './About'; -import { IAiConfig } from '@/typings'; import styles from './index.less'; import { ILatestVersion } from '@/service/config'; import UpdateDetection, { IUpdateDetectionRef, UpdatedStatusEnum } from '@/blocks/Setting/UpdateDetection'; // ---- store ----- -import { useSettingStore, getAiSystemConfig, setAiSystemConfig } from '@/store/setting' +import { useSettingStore, getAiSystemConfig, setAiSystemConfig } from '@/store/setting'; interface IProps { - aiConfig: IAiConfig; className?: string; render?: ReactNode; - dispatch: (params: any) => void; noLogin?: boolean; // 用于在没有登录的页面使用,不显示ai设置等需要登录的功能 defaultArouse?: boolean; // 是否默认弹出 defaultMenu?: number; // 默认选中的菜单 @@ -35,7 +32,7 @@ function Setting(props: IProps) { const [currentMenu, setCurrentMenu] = useState(defaultMenu); const [updateDetectionData, setUpdateDetectionData] = useState(null); const updateDetectionRef = React.useRef(null); - const aiConfig = useSettingStore(state => state.aiConfig); + const aiConfig = useSettingStore((state) => state.aiConfig); useEffect(() => { if (defaultArouse) { @@ -166,9 +163,4 @@ function Setting(props: IProps) { ); } -// export default connect(({ ai }: { ai: IAIState }) => ({ -// aiConfig: ai.aiConfig, -// }))(); - export default Setting; - diff --git a/chat2db-client/src/blocks/Tree/index.tsx b/chat2db-client/src/blocks/Tree/index.tsx index e870a2701..c051c3605 100644 --- a/chat2db-client/src/blocks/Tree/index.tsx +++ b/chat2db-client/src/blocks/Tree/index.tsx @@ -283,18 +283,17 @@ const TreeNode = memo((props: TreeNodeIProps) => { ); }, [isFocus, isLoading, rightClickMenu]); - const sectionHeight = useMemo(() => { - console.log(treeNodeData.total); - - if (treeNodeData.total) { - return `${treeNodeData.total * 26}px`; - } else { - return 'auto'; - } - }, [treeNodeData.total]); + // const sectionHeight = useMemo(() => { + // if (treeNodeData.total && treeNodeData.children) { + // return `${treeNodeData.total * 26}px`; + // } else { + // return 'auto'; + // } + // }, [treeNodeData.total, treeNodeData.children]); return ( -
+ // style={{ height: sectionHeight }} +
{(showTreeNode || showParentNode) && treeNodeDom} {treeNodes}
diff --git a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx index 79a4e3322..0d60b8188 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/SelectBoundInfo/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState, memo, useContext } from 'react'; -import { IBoundInfo, IntelligentEditorContext } from '../../index'; +import { IntelligentEditorContext } from '../../index'; import { Dropdown } from 'antd'; import { useConnectionStore } from '@/pages/main/store/connection'; import connectionService from '@/service/connection'; @@ -8,6 +8,7 @@ import Iconfont from '@/components/Iconfont'; import { databaseMap } from '@/constants/database'; import styles from './index.less'; import sqlService from '@/service/sql'; +import { IBoundInfo } from '@/typings'; import { registerIntelliSenseField, @@ -67,7 +68,7 @@ const SelectBoundInfo = memo((props: IProps) => { // 当数据源变化时,重新获取数据库列表 useEffect(() => { - if (!isActive) { + if (!isActive || boundInfo.connectable === false) { return; } if (supportDatabase) { @@ -82,7 +83,7 @@ const SelectBoundInfo = memo((props: IProps) => { // 当数据库名变化时,重新获取schema列表 useEffect(() => { - if (!isActive) { + if (!isActive || boundInfo.connectable === false) { return; } if (supportSchema) { @@ -94,7 +95,7 @@ const SelectBoundInfo = memo((props: IProps) => { }, [boundInfo.databaseName, isActive]); useEffect(() => { - if (!isActive) { + if (!isActive || boundInfo.connectable === false) { return; } if (supportSchema && boundInfo.schemaName) { diff --git a/chat2db-client/src/components/ConsoleEditor/index.tsx b/chat2db-client/src/components/ConsoleEditor/index.tsx index 08c7e1954..280cb3996 100644 --- a/chat2db-client/src/components/ConsoleEditor/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/index.tsx @@ -15,8 +15,7 @@ import ChatInput, { SyncModelType } from './components/ChatInput'; import MonacoEditor, { IEditorOptions, IExportRefFunction, IRangeType } from '../MonacoEditor'; import aiServer from '@/service/ai'; import { v4 as uuidv4 } from 'uuid'; -import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; -import { IAiConfig } from '@/typings'; +import { IAiConfig, IBoundInfo } from '@/typings'; import Popularize from '@/components/Popularize'; import OperationLine from './components/OperationLine'; import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; @@ -47,16 +46,6 @@ export type IAppendValue = { range?: IRangeType; }; -export interface IBoundInfo { - consoleId?: number; - dataSourceId: number; - dataSourceName: string; - databaseType: DatabaseTypeCode; - databaseName?: string; - schemaName?: string; - status: ConsoleStatus; -} - interface IProps { /** 调用来源 */ source?: 'workspace'; diff --git a/chat2db-client/src/hooks/getConnection.ts b/chat2db-client/src/hooks/getConnection.ts index 30569ba25..a217db62e 100644 --- a/chat2db-client/src/hooks/getConnection.ts +++ b/chat2db-client/src/hooks/getConnection.ts @@ -1,28 +1,31 @@ import connectionService from '@/service/connection'; import { setConnectionEnvList, getConnectionList } from '@/pages/main/store/connection'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; + +const getConnectionEnvList = () => { + connectionService.getEnvList().then((res) => { + setConnectionEnvList(res); + }); +}; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; const getConnection = () => { const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; - const getConnectionEnvList = () => { - connectionService.getEnvList().then((res) => { - setConnectionEnvList(res); - }); - }; getConnectionList().then((res) => { // 如果连接列表为空,则设置当前连接为空 - if(res.length === 0){ + if (res.length === 0) { setCurrentConnectionDetails(null); return; } + // 如果当前连接不存在,则设置当前连接为第一个连接 if (!currentConnectionDetails?.id) { setCurrentConnectionDetails(res[0]); return; } + // 如果存在但是不在列表中,则设置当前连接为第一个连接 const currentConnection = res.find((item) => item.id === currentConnectionDetails?.id); if (!currentConnection) { diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index a46ce4ee8..be89ac6f8 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -31,4 +31,5 @@ export default { 'workspace.tree.search.placeholder': 'Search in the expand node', 'workspace.tree.delete.tip': 'I understand that this operation is permanently deleted', 'workspace.tree.delete.table.tip': 'Are you sure you want to delete the table {1}?', + 'workspace.tips.noConnection': 'You have not created a connection yet', }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 7c0433fef..a82ee82fe 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -30,5 +30,6 @@ export default { 'workspace.tree.search.placeholder': '在展开节点中搜索', 'workspace.tree.delete.tip': '我了解该操作是永久性删除', 'workspace.tree.delete.table.tip': '确定要删除表{1}吗?', + 'workspace.tips.noConnection': '你还没有创建连接', }; diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index a0f8eb362..884b739dc 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -26,7 +26,6 @@ import { useConnectionStore, getConnectionList } from '@/pages/main/store/connec import { setMainPageActiveTab } from '@/pages/main/store/main'; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; - import styles from './index.less'; const ConnectionsPage = () => { @@ -50,7 +49,7 @@ const ConnectionsPage = () => { // 处理列表双击事件 const handleMenuItemDoubleClick = (t: IConnectionListItem) => { setCurrentConnectionDetails(t); - setMainPageActiveTab('workspace') + setMainPageActiveTab('workspace'); }; // 处理列表单击和双击事件 @@ -81,6 +80,7 @@ const ConnectionsPage = () => { getConnectionList(); if (connectionActiveId === t.id) { setConnectionActiveId(null); + setConnectionDetail(null); } }); }; diff --git a/chat2db-client/src/pages/main/functions/getConnection.ts b/chat2db-client/src/pages/main/functions/getConnection.ts new file mode 100644 index 000000000..1dd706cdc --- /dev/null +++ b/chat2db-client/src/pages/main/functions/getConnection.ts @@ -0,0 +1,10 @@ +import connectionService from '@/service/connection'; +import { setConnectionEnvList } from '@/pages/main/store/connection'; + +const getConnectionEnvList = () => { + connectionService.getEnvList().then((res) => { + setConnectionEnvList(res); + }); +}; + +export default getConnectionEnvList; diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 594f58aed..564a98e31 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -12,10 +12,11 @@ import { INavItem } from '@/typings/main'; import { ILoginUser, IRole } from '@/typings/user'; // ----- hooks ----- -import getConnection from '@/hooks/getConnection'; +import getConnectionEnvList from './functions/getConnection'; // ----- store ----- import { useMainStore, setMainPageActiveTab } from '@/pages/main/store/main'; +import { getConnectionList } from '@/pages/main/store/connection'; // ----- block ----- import Workspace from './workspace'; @@ -66,16 +67,17 @@ function MainPage() { const navigate = useNavigate(); const [navConfig, setNavConfig] = useState(initNavConfig); const [userInfo, setUserInfo] = useState(); - const mainPageActiveTab = useMainStore(state=> state.mainPageActiveTab) + const mainPageActiveTab = useMainStore((state) => state.mainPageActiveTab); const [activeNavKey, setActiveNavKey] = useState(window.location.pathname.split('/')[1] || mainPageActiveTab); useEffect(() => { handleInitPage(); - getConnection(); + getConnectionList(); + getConnectionEnvList(); }, []); useUpdateEffect(() => { - switchingNav(mainPageActiveTab) + switchingNav(mainPageActiveTab); }, [mainPageActiveTab]); // 切换tab @@ -117,7 +119,7 @@ function MainPage() { const switchingNav = (key: string) => { setActiveNavKey(key); - setMainPageActiveTab(key) + setMainPageActiveTab(key); }; const handleLogout = () => { diff --git a/chat2db-client/src/pages/main/store/connection/index.ts b/chat2db-client/src/pages/main/store/connection/index.ts index c0ce9e393..4cd516f58 100644 --- a/chat2db-client/src/pages/main/store/connection/index.ts +++ b/chat2db-client/src/pages/main/store/connection/index.ts @@ -5,6 +5,11 @@ import { StoreApi } from 'zustand'; import { IConnectionListItem, IConnectionEnv } from '@/typings/connection'; import connectionService from '@/service/connection'; + +import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; +import { useWorkspaceStore } from '@/pages/main/workspace/store'; +import { getSavedConsoleList } from '@/pages/main/workspace/store/console'; + export interface IConnectionStore { connectionList: IConnectionListItem[] | null; connectionEnvList: IConnectionEnv[] | null; @@ -17,7 +22,7 @@ export const initConnectionStore = { export const useConnectionStore: UseBoundStoreWithEqualityFn> = createWithEqualityFn( devtools(() => initConnectionStore), - shallow + shallow, ); export const setConnectionList = (connectionList: IConnectionListItem[]) => { @@ -27,8 +32,10 @@ export const setConnectionList = (connectionList: IConnectionListItem[]) => { export const setConnectionEnvList = (connectionEnvList: IConnectionEnv[]) => { return useConnectionStore.setState({ connectionEnvList }); }; + export const getConnectionList: () => Promise = () => { return new Promise((resolve, reject) => { + const currentConnectionDetails = useWorkspaceStore.getState().currentConnectionDetails; connectionService .getList({ pageNo: 1, @@ -39,6 +46,26 @@ export const getConnectionList: () => Promise = () => { const connectionList = res?.data || []; useConnectionStore.setState({ connectionList }); resolve(connectionList); + // 连接删除后需要更新下 consoleList + getSavedConsoleList(); + + // 如果连接列表为空,则设置当前连接为空 + if (connectionList.length === 0) { + setCurrentConnectionDetails(null); + return; + } + + // 如果当前连接不存在,则设置当前连接为第一个连接 + if (!currentConnectionDetails?.id) { + setCurrentConnectionDetails(connectionList[0]); + return; + } + + // 如果存在但是不在列表中,则设置当前连接为第一个连接 + const currentConnection = connectionList.find((item) => item.id === currentConnectionDetails?.id); + if (!currentConnection) { + setCurrentConnectionDetails(connectionList[0]); + } }) .catch(() => { useConnectionStore.setState({ connectionList: [] }); diff --git a/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx b/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx index 9bd3bc087..6340e6ddc 100644 --- a/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SQLExecute/index.tsx @@ -4,19 +4,11 @@ import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import ConsoleEditor, { IConsoleRef } from '@/components/ConsoleEditor'; import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; -import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; +import { IBoundInfo } from '@/typings'; interface IProps { - boundInfo: { - dataSourceId: number; - dataSourceName: string; - databaseType: DatabaseTypeCode; - databaseName?: string; - schemaName?: string; - status: ConsoleStatus; - consoleId: number; - }; + boundInfo: IBoundInfo; initDDL: string; // 异步加载sql loadSQL: () => Promise; @@ -27,7 +19,7 @@ const SQLExecute = memo((props) => { const draggableRef = useRef(); const searchResultRef = useRef(null); const consoleRef = useRef(null); - const [boundInfo, setBoundInfo] = useState(_boundInfo); + const [boundInfo, setBoundInfo] = useState(_boundInfo); const activeConsoleId = useWorkspaceStore((state) => state.activeConsoleId); useEffect(() => { diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx index 6fa0500e4..b570dfdaa 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -25,6 +25,7 @@ export default memo((props) => { const getTreeData = (refresh = false) => { if (!currentConnectionDetails?.id) { + setTreeData([]); return; } setTreeData(null); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less index c0658c5c6..ba1f64952 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -9,6 +9,29 @@ min-width: 200px; } +.noConnectionList { + height: 100%; + margin-top: 30vh; + text-align: center; + font-size: 14px; + .noConnectionListIcon { + font-size: 60px; + color: var(--color-primary); + } + .noConnectionListTips { + margin: 10px 0px; + } + .create { + color: var(--color-primary); + text-decoration: underline; + cursor: pointer; + margin-right: 4px; + &:hover { + color: var(--color-primary-hover); + } + } +} + .createButtonBox { padding: 10px 0px 10px; } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx index 7c0f442db..01579e396 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -1,16 +1,45 @@ import React, { memo } from 'react'; +import i18n from '@/i18n'; import classnames from 'classnames'; import styles from './index.less'; -import NewTableList from '../TableList'; +import TableList from '../TableList'; import WorkspaceLeftHeader from '../WorkspaceLeftHeader'; import CreateDatabase from '@/components/CreateDatabase'; +import Iconfont from '@/components/Iconfont'; +import { useConnectionStore } from '@/pages/main/store/connection'; +import { setMainPageActiveTab } from '@/pages/main/store/main'; const WorkspaceLeft = memo(() => { + const { connectionList } = useConnectionStore((state) => { + return { + connectionList: state.connectionList, + }; + }); + + const jumpPage = () => { + setMainPageActiveTab('connections'); + }; + return ( <>
- - + {connectionList?.length ? ( + <> + + + + ) : ( +
+ +
{i18n('workspace.tips.noConnection')}
+
+ + {i18n('common.title.create')} + + {i18n('connection.title.connections')} +
+
+ )}
diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less index 349642a1b..bc6de2c9d 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.less @@ -4,13 +4,13 @@ } .selectConnection { - // margin: 4px 4px; padding: 6px; - // border-radius: 4px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; + box-sizing: border-box; + height: 32px; .menuLabel { flex: 1; width: 0px; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx index 0af4f45df..7dae67f9c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeftHeader/index.tsx @@ -16,12 +16,7 @@ import { databaseMap } from '@/constants'; import { IConnectionListItem } from '@/typings/connection'; -interface IProps { - className?: string; -} - -export default memo((props) => { - const { className } = props; +export default memo(() => { const { connectionList } = useConnectionStore((state) => { return { connectionList: state.connectionList, @@ -34,6 +29,8 @@ export default memo((props) => { }; }); + console.log('currentConnectionDetails', currentConnectionDetails); + const renderConnectionLabel = (item: IConnectionListItem) => { return (
@@ -64,15 +61,13 @@ export default memo((props) => { }, [connectionList, currentConnectionDetails]); return ( -
- -
- {currentConnectionDetails && renderConnectionLabel(currentConnectionDetails)} -
- -
+ +
+ {currentConnectionDetails && renderConnectionLabel(currentConnectionDetails)} +
+
- -
+
+
); }); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx index 9448d4e39..cc18a7b6e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx @@ -63,6 +63,7 @@ const WorkspaceTabs = memo(() => { schemaName: item.schemaName, status: item.status, ddl: item.ddl, + connectable: item.connectable, }, }; }) || []; @@ -150,6 +151,7 @@ const WorkspaceTabs = memo(() => { schemaName: uniqueData?.schemaName, consoleId: item.id as number, status: uniqueData.status, + connectable: uniqueData.connectable, }} initDDL={uniqueData.ddl} loadSQL={uniqueData.loadSQL} @@ -183,6 +185,7 @@ const WorkspaceTabs = memo(() => { // 渲染所有表 const renderViewAllTable = (item: IWorkspaceTab) => { const { uniqueData } = item; + console.log('uniqueData', uniqueData); return ; }; diff --git a/chat2db-client/src/pages/main/workspace/store/console.ts b/chat2db-client/src/pages/main/workspace/store/console.ts index ae7954068..daf9698fb 100644 --- a/chat2db-client/src/pages/main/workspace/store/console.ts +++ b/chat2db-client/src/pages/main/workspace/store/console.ts @@ -55,20 +55,6 @@ export const createConsole = (params: ICreateConsoleParams) => { } useWorkspaceStore.setState({ createConsoleLoading: true }); historyService.createConsole(newConsole).then((res) => { - // 找到活跃的id的位置 - // const activeIndex = workspaceTabList?.findIndex( - // (item) => item?.id === useWorkspaceStore.getState().activeConsoleId, - // ); - // // 向活跃的位置后插入数据 - // if (activeIndex !== -1) { - // workspaceTabList?.splice(activeIndex + 1, 0, { - // id: res, - // title: newConsole.name, - // type: newConsole.operationType, - // uniqueData: newConsole, - // }); - // } - const newList = [ ...(workspaceTabList || []), { diff --git a/chat2db-client/src/typings/workspace.ts b/chat2db-client/src/typings/workspace.ts index fb06436df..54f4f5779 100644 --- a/chat2db-client/src/typings/workspace.ts +++ b/chat2db-client/src/typings/workspace.ts @@ -1,4 +1,4 @@ -import { CreateTabIntroType, WorkspaceTabType } from '@/constants'; +import { CreateTabIntroType, WorkspaceTabType, DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { ITreeNode } from '@/typings'; @@ -15,3 +15,14 @@ export interface IWorkspaceTab { uniqueData?: any; } +export interface IBoundInfo { + consoleId?: number; + dataSourceId: number; + dataSourceName: string; + databaseType: DatabaseTypeCode; + databaseName?: string; + schemaName?: string; + status: ConsoleStatus; + connectable: boolean; +} + From 2627140c816d994f466be1432701652ea10f67aa Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 1 Dec 2023 12:11:35 +0800 Subject: [PATCH 109/126] WorkspaceExtend --- .../components/OperationLine/index.tsx | 2 +- .../ConsoleEditor/hooks/useSaveEditorData.ts | 2 + .../src/components/Modal/BaseModal/index.tsx | 70 +++-- .../src/components/Output/index.less | 20 +- .../src/components/Output/index.tsx | 9 +- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/en-us/workspace.ts | 2 +- chat2db-client/src/i18n/zh-cn/common.ts | 1 + chat2db-client/src/i18n/zh-cn/workspace.ts | 2 +- .../src/pages/main/store/connection/index.ts | 4 +- .../workspace/components/SaveList/index.less | 17 +- .../workspace/components/SaveList/index.tsx | 245 +++++++++--------- .../components/WorkspaceExtend/index.less | 10 +- .../components/WorkspaceExtend/index.tsx | 19 +- .../components/WorkspaceRight/index.less | 11 +- .../components/WorkspaceRight/index.tsx | 2 + .../components/WorkspaceTabs/index.less | 11 +- .../components/WorkspaceTabs/index.tsx | 4 +- .../src/pages/main/workspace/store/config.ts | 2 +- .../src/pages/main/workspace/store/console.ts | 21 +- chat2db-client/src/service/history.ts | 4 +- 21 files changed, 254 insertions(+), 205 deletions(-) diff --git a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx index df38f43f9..cf47b335a 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import i18n from '@/i18n'; import { Button } from 'antd'; -import { IBoundInfo } from '../../index'; +import { IBoundInfo } from '@/typings/workspace'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import SelectBoundInfo from '../SelectBoundInfo'; diff --git a/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts b/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts index d9cb55328..761c70358 100644 --- a/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts +++ b/chat2db-client/src/components/ConsoleEditor/hooks/useSaveEditorData.ts @@ -5,6 +5,7 @@ import indexedDB from '@/indexedDB'; import historyServer from '@/service/history'; import i18n from '@/i18n'; import { getCookie } from '@/utils'; +import { getSavedConsoleList } from '@/pages/main/workspace/store/console'; interface IProps { @@ -30,6 +31,7 @@ export const useSaveEditorData = (props: IProps) => { }; historyServer.updateSavedConsole(p).then(() => { + getSavedConsoleList(); indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', boundInfo.consoleId!); lastSyncConsole.current = value; setSaveStatus(ConsoleStatus.RELEASE); diff --git a/chat2db-client/src/components/Modal/BaseModal/index.tsx b/chat2db-client/src/components/Modal/BaseModal/index.tsx index 90593032a..ad3a82a54 100644 --- a/chat2db-client/src/components/Modal/BaseModal/index.tsx +++ b/chat2db-client/src/components/Modal/BaseModal/index.tsx @@ -1,45 +1,61 @@ -import React, { memo, useEffect, useState } from 'react'; +import React, { memo, useEffect, useMemo, useState } from 'react'; import { Modal as AntdModal } from 'antd'; import { injectOpenModal } from '@/store/common/components'; -export type IModalData = { - title?: string; - width?: string; - footer?: React.ReactNode | false; - content: React.ReactNode | false -} | null | false +export type IModalData = + | { + title?: string; + width?: string; + onOk?: () => void; + footer?: React.ReactNode | false; + content: React.ReactNode | false; + } + | null + | false; const Modal = memo(() => { const [open, setOpen] = useState(false); const [modalData, setModalData] = useState(null); - const openModal = (params:IModalData)=>{ - if(params === false){ - setOpen(false) - }else{ - setOpen(true) - setModalData(params) + const openModal = (params: IModalData) => { + if (params === false) { + setOpen(false); + } else { + setOpen(true); + setModalData(params); } - } + }; useEffect(() => { injectOpenModal(openModal); }, []); + const footer = useMemo(() => { + if (modalData && modalData.footer) { + return { + footer: modalData.footer, + onOk: modalData.onOk, + }; + } else { + return {}; + } + }, [modalData]); + return ( - !!modalData && - { - setOpen(false); - }} - destroyOnClose={true} - footer={modalData.footer || false} - > - {modalData.content} - + !!modalData && ( + { + setOpen(false); + }} + destroyOnClose={true} + {...footer} + > + {modalData.content} + + ) ); }); diff --git a/chat2db-client/src/components/Output/index.less b/chat2db-client/src/components/Output/index.less index 1a31e1c7b..0b9407585 100644 --- a/chat2db-client/src/components/Output/index.less +++ b/chat2db-client/src/components/Output/index.less @@ -10,13 +10,14 @@ position: sticky; top: 0; z-index: 1; - background-color: var(--color-bg-subtle); - border-bottom: 1px solid var(--color-border); - line-height: 32px; display: flex; align-items: center; - font-size: 14px; - padding: 0px 4px; + + background-color: var(--color-bg-subtle); + line-height: 32px; + padding: 0px 10px; + font-weight: bold; + border-bottom: 1px solid var(--color-border); i { margin-right: 6px; } @@ -31,7 +32,7 @@ display: flex; align-items: center; } - .timeSpan{ + .timeSpan { margin-right: 4px; font-weight: 500; } @@ -42,7 +43,7 @@ color: var(--color-success); } } - .failureIconBox{ + .failureIconBox { i { color: var(--color-error); } @@ -50,10 +51,11 @@ > div { line-height: 22px; } - .sqlPlace{ + .sqlPlace { color: var(--color-warning-text); } - .sqlPlace,.sqlBox{ + .sqlPlace, + .sqlBox { padding-left: 18px; } padding: 2px 0px; diff --git a/chat2db-client/src/components/Output/index.tsx b/chat2db-client/src/components/Output/index.tsx index 973fb2764..e9cf1396f 100644 --- a/chat2db-client/src/components/Output/index.tsx +++ b/chat2db-client/src/components/Output/index.tsx @@ -9,7 +9,6 @@ import i18n from '@/i18n'; interface IProps { className?: string; - curWorkspaceParams: any; } interface IDatasource extends IHistoryRecord { @@ -22,11 +21,11 @@ export default memo((props) => { const outputContentRef = React.useRef(null); const curPageRef = React.useRef(1); const finishedRef = React.useRef(false); - + const getHistoryList = () => { return historyService .getHistoryList({ - dataSourceId:props.curWorkspaceParams.dataSourceId, + // dataSourceId:props.curWorkspaceParams.dataSourceId, pageNo: curPageRef.current++, pageSize: 40, }) @@ -53,7 +52,7 @@ export default memo((props) => { return (
- + {/* */} {i18n('common.title.executiveLogging')}
@@ -74,7 +73,7 @@ export default memo((props) => {
[{item.gmtCreate}] {/* {!!item.operationRows && {item.operationRows} rows} */} - {!!item.useTime && {i18n('common.text.executionTime',item.useTime)}} + {!!item.useTime && {i18n('common.text.executionTime', item.useTime)}}
{nameList.filter((name) => name).join(' > ')}
diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 411772f90..3f920c53b 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -118,4 +118,5 @@ export default { 'common.button.closeOthers': 'Close others', 'common.label.tcp': 'TCP', 'common.label.LocalFile': 'LocalFile', + 'common.text.rename': 'Rename', }; diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index be89ac6f8..d48293565 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -2,7 +2,7 @@ export default { 'workspace.title': 'Workspace', 'workspace.cascader.placeholder': 'Select Here', 'workspace.ai.input.placeholder': 'Enter your plain text statement here', - 'workspace.title.saved': 'Saved', + 'workspace.title.savedConsole': 'Saved console', 'workspace.menu.ViewDDL': 'View DDL', 'workspace.menu.deleteTable': 'Delete Table', 'workspace.menu.openTable': 'Open Table', diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index dbe77910e..a7733e046 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -117,4 +117,5 @@ export default { 'common.button.closeOthers': '关闭其他', 'common.label.tcp': '线上', 'common.label.LocalFile': '本地', + 'common.text.rename': '重命名', }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index a82ee82fe..e57d40d5e 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -2,7 +2,7 @@ export default { 'workspace.title': '工作台', 'workspace.cascader.placeholder': '请选择', 'workspace.ai.input.placeholder': '在这里输入纯文本语句', - 'workspace.title.saved': '保存记录', + 'workspace.title.savedConsole': '保存记录', 'workspace.menu.ViewDDL': '查看DDL', 'workspace.menu.deleteTable': '删除表', 'workspace.menu.openTable': '打开表', diff --git a/chat2db-client/src/pages/main/store/connection/index.ts b/chat2db-client/src/pages/main/store/connection/index.ts index 4cd516f58..84e3e7f51 100644 --- a/chat2db-client/src/pages/main/store/connection/index.ts +++ b/chat2db-client/src/pages/main/store/connection/index.ts @@ -8,7 +8,7 @@ import connectionService from '@/service/connection'; import { setCurrentConnectionDetails } from '@/pages/main/workspace/store/common'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; -import { getSavedConsoleList } from '@/pages/main/workspace/store/console'; +import { getOpenConsoleList } from '@/pages/main/workspace/store/console'; export interface IConnectionStore { connectionList: IConnectionListItem[] | null; @@ -47,7 +47,7 @@ export const getConnectionList: () => Promise = () => { useConnectionStore.setState({ connectionList }); resolve(connectionList); // 连接删除后需要更新下 consoleList - getSavedConsoleList(); + getOpenConsoleList(); // 如果连接列表为空,则设置当前连接为空 if (connectionList.length === 0) { diff --git a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less index a6da9e00b..94e2c6b64 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less @@ -2,6 +2,9 @@ .saveModule { flex-shrink: 0; + height: 100%; + display: flex; + flex-direction: column; } .leftModuleTitle { @@ -11,6 +14,7 @@ height: 32px; display: flex; align-items: center; + border-bottom: 1px solid var(--color-border); .leftModuleTitleText { width: 100%; display: flex; @@ -51,8 +55,9 @@ } .saveBoxList { - padding: 0px 10px; - height: calc(26px * 3 + 6px); + flex: 1; + height: 0px; + padding: 0px 4px; overflow-y: hidden; &:hover { overflow-y: auto; @@ -89,17 +94,9 @@ .f-single-line(); } } - .moreButton { - flex-shrink: 0; - display: none; - transform: rotate(90deg); - } &:hover { background-color: var(--color-hover-bg); color: var(--color-primary); - .moreButton { - display: block; - } } } diff --git a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx index 834bc0bfe..846e67870 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -1,66 +1,31 @@ -import React, { memo, useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import i18n from '@/i18n'; -import { connect } from 'umi'; -import { Input, Dropdown } from 'antd'; +import { Input, Dropdown, Modal } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; -import { IWorkspaceModelType } from '@/models/workspace'; import historyServer from '@/service/history'; -import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType, workspaceTabConfig } from '@/constants'; +import { ConsoleOpenedStatus, workspaceTabConfig } from '@/constants'; import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateList } from '@/utils'; +import { addWorkspaceTab, getSavedConsoleList } from '@/pages/main/workspace/store/console'; +import { useWorkspaceStore } from '@/pages/main/workspace/store'; +import MenuLabel from '@/components/MenuLabel'; +import { set } from 'lodash'; -const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace, -})); - -const SaveList = dvaModel((props: any) => { - const { workspaceModel, dispatch } = props; - const { curWorkspaceParams, consoleList } = workspaceModel; +const SaveList = () => { const [searching, setSearching] = useState(false); const inputRef = useRef(); const [searchedList, setSearchedList] = useState(); const leftModuleTitleRef = useRef(null); const saveBoxListRef = useRef(null); + const consoleList = useWorkspaceStore((state) => state.savedConsoleList); + const [editData, setEditData] = useState(null); - // 监听saveBoxListRef滚动时,给leftModuleTitle添加下阴影 useEffect(() => { - const saveBoxList = saveBoxListRef.current; - const leftModuleTitle = leftModuleTitleRef.current; - if (saveBoxList && leftModuleTitle) { - saveBoxList.addEventListener('scroll', () => { - if (saveBoxList.scrollTop > 0) { - leftModuleTitle.classList.add(styles.leftModuleTitleShadow); - } else { - leftModuleTitle.classList.remove(styles.leftModuleTitleShadow); - } - }); - } + getSavedConsoleList(); }, []); - useEffect(() => { - if (!curWorkspaceParams.dataSourceId || !(curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName)) { - return; - } - dispatch({ - type: 'workspace/fetchGetSavedConsole', - payload: { - pageNo: 1, - pageSize: 999, - orderByDesc: true, - status: ConsoleStatus.RELEASE, - ...curWorkspaceParams, - }, - callback: (res: any) => { - dispatch({ - type: 'workspace/setConsoleList', - payload: res.data, - }); - }, - }); - }, [curWorkspaceParams]); - useEffect(() => { if (searching) { inputRef.current!.focus({ @@ -81,22 +46,30 @@ const SaveList = dvaModel((props: any) => { } function onChange(value: string) { - setSearchedList(approximateList(consoleList, value)); + if (consoleList) { + setSearchedList(approximateList(consoleList as any, value)); + } } - function openConsole(data: IConsole) { + function openConsole(item: IConsole) { const params: any = { - id: data.id, + id: item.id, tabOpened: ConsoleOpenedStatus.IS_OPEN, }; - historyServer.updateSavedConsole(params).then((res) => { - dispatch({ - type: 'workspace/setCreateConsoleIntro', - payload: { - id: data.id, - type: WorkspaceTabType.CONSOLE, - title: data.name, - uniqueData: data, + historyServer.updateSavedConsole(params).then(() => { + addWorkspaceTab({ + id: item.id, + type: item.operationType, + title: item.name, + uniqueData: { + dataSourceId: item.dataSourceId, + dataSourceName: item.dataSourceName, + databaseType: item.type, + databaseName: item.databaseName, + schemaName: item.schemaName, + status: item.status, + ddl: item.ddl, + connectable: item.connectable, }, }); }); @@ -105,85 +78,72 @@ const SaveList = dvaModel((props: any) => { function deleteSaved(data: IConsole) { const params: any = { id: data.id, - status: ConsoleStatus.DRAFT, }; - historyServer.updateSavedConsole(params).then(() => { - dispatch({ - type: 'workspace/fetchGetSavedConsole', - payload: { - orderByDesc: true, - status: ConsoleStatus.RELEASE, - ...curWorkspaceParams, - }, - callback: (_res: any) => { - dispatch({ - type: 'workspace/setConsoleList', - payload: _res.data, - }); - }, - }); + historyServer.deleteSavedConsole(params).then(() => { + getSavedConsoleList(); }); } + const editSaved = (data: IConsole) => { + setEditData(data); + }; + return ( -
-
- {searching ? ( -
- } - onBlur={onBlur} - onChange={(e) => onChange(e.target.value)} - allowClear - /> -
- ) : ( -
-
{i18n('workspace.title.saved')}
-
- {/*
refreshTableList()}> + <> +
+
+ {searching ? ( +
+ } + onBlur={onBlur} + onChange={(e) => onChange(e.target.value)} + allowClear + /> +
+ ) : ( +
+
{i18n('workspace.title.savedConsole')}
+
+ {/*
refreshTableList()}>
*/} -
openSearch()}> - +
openSearch()}> + +
-
- )} -
-
- - {(searchedList || consoleList)?.map((t: IConsole) => { - return ( -
{ - openConsole(t); - }} - key={t.id} - className={styles.saveItem} - > -
-
- -
-
-
+ )} +
+
+ + {(searchedList || consoleList)?.map((t) => { + return ( , onClick: () => { openConsole(t); }, }, + { + key: 'edit', + label: , + onClick: () => { + editSaved(t); + }, + }, { key: 'delete', - label: i18n('common.button.delete'), + label: , onClick: () => { deleteSaved(t); }, @@ -191,17 +151,52 @@ const SaveList = dvaModel((props: any) => { ], }} > -
- +
{ + openConsole(t); + }} + className={styles.saveItem} + > +
+
+ +
+
+
-
- ); - })} - + ); + })} + +
-
+ { + const params: any = { + id: editData.id, + name: editData.name, + }; + historyServer.updateSavedConsole(params).then(() => { + getSavedConsoleList(); + setEditData(null); + }); + }} + onCancel={() => setEditData(null)} + > + { + setEditData({ + ...editData, + name: e.target.value, + }); + }} + /> + + ); -}); +}; -export default dvaModel(SaveList); +export default SaveList; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less index 2cad599ac..36bd76553 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less @@ -9,15 +9,17 @@ display: flex; flex-direction: column; align-items: center; - justify-content: space-between; - padding: 10px 0px; + padding: 5px 0px; } .workspaceExtendMain { - width: 400px; border-right: 1px solid var(--color-border); } } -.workspaceExtendActive{ +.rightBarFront { + margin: 2px 0px; +} + +.workspaceExtendActive { display: flex; } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx index 3a13019b4..c51daf445 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx @@ -4,12 +4,12 @@ import classnames from 'classnames'; import { Popover } from 'antd'; import Iconfont from '@/components/Iconfont'; import Output from '@/components/Output'; +import SaveList from '../SaveList'; import { useWorkspaceStore } from '@/pages/main/workspace/store'; import i18n from '@/i18n'; interface IProps { className?: string; - curWorkspaceParams: any; } interface IToolbar { @@ -17,6 +17,7 @@ interface IToolbar { title: string; icon: string; components: any; + width: number; } export default memo((props) => { @@ -35,7 +36,15 @@ export default memo((props) => { code: 'executiveLog', title: i18n('common.title.executiveLogging'), icon: '\ue8ad', - components: , + components: , + width: 400, + }, + { + code: 'saveList', + title: i18n('workspace.title.savedConsole'), + icon: '\ue619', + components: , + width: 200, }, ]; @@ -53,7 +62,11 @@ export default memo((props) => { [styles.workspaceExtendActive]: panelRight, })} > - {activeExtend &&
{activeExtend?.components}
} + {activeExtend && ( +
+ {activeExtend?.components} +
+ )}
{toolbarConfig.map((item, index) => { return ( diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less index cccc96a23..3fe2b15d9 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -5,14 +5,19 @@ flex: 1; width: 0px; display: flex; - flex-direction: column; } -.consoleTabsContainer{ +.workspaceExtend { + flex-shrink: 0; + border-left: 1px solid var(--color-border); + display: flex; +} + +.consoleTabsContainer { height: 50%; } -.searchResultContainer{ +.searchResultContainer { border-top: 1px solid var(--color-border-secondary); flex: 1; } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx index 7ba7949ba..7fb85f246 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -1,6 +1,7 @@ import React, { memo } from 'react'; import styles from './index.less'; import classnames from 'classnames'; +import WorkspaceExtend from '../WorkspaceExtend'; // ----- components ----- import WorkspaceTabs from '../WorkspaceTabs'; @@ -9,6 +10,7 @@ const WorkspaceRight = memo(() => { return (
+
); }); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less index 82294b0ad..f0856b4eb 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.less @@ -2,19 +2,18 @@ .tabBox { height: 100%; + width: 0px; + flex: 1; } .ears { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + flex: 1; + width: 0px; display: flex; justify-content: center; align-items: center; overflow: hidden; - button{ + button { display: flex; align-items: center; } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx index cc18a7b6e..2396d61d9 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceTabs/index.tsx @@ -19,7 +19,7 @@ import ShortcutKey from '@/components/ShortcutKey'; // ---- store ----- import { - getSavedConsoleList, + getOpenConsoleList, setActiveConsoleId, setWorkspaceTabList, createConsole, @@ -44,7 +44,7 @@ const WorkspaceTabs = memo(() => { // 获取console useEffect(() => { - getSavedConsoleList(); + getOpenConsoleList(); }, []); // consoleList 先转换为通用的 workspaceTabList diff --git a/chat2db-client/src/pages/main/workspace/store/config.ts b/chat2db-client/src/pages/main/workspace/store/config.ts index b37e3cf70..44c967248 100644 --- a/chat2db-client/src/pages/main/workspace/store/config.ts +++ b/chat2db-client/src/pages/main/workspace/store/config.ts @@ -10,7 +10,7 @@ export interface IConfigStore { export const initConfigStore: IConfigStore = { layout: { panelLeft: true, - panelRight: false, + panelRight: true, panelLeftWidth: 220, }, } diff --git a/chat2db-client/src/pages/main/workspace/store/console.ts b/chat2db-client/src/pages/main/workspace/store/console.ts index daf9698fb..1f85055c9 100644 --- a/chat2db-client/src/pages/main/workspace/store/console.ts +++ b/chat2db-client/src/pages/main/workspace/store/console.ts @@ -6,6 +6,7 @@ import { ConsoleStatus, WorkspaceTabType } from '@/constants'; export interface IConsoleStore { consoleList: IConsole[] | null; + savedConsoleList: IConsole[] | null; activeConsoleId: string | number | null; workspaceTabList: IWorkspaceTab[] | null; createConsoleLoading: boolean @@ -13,14 +14,16 @@ export interface IConsoleStore { export const initConsoleStore = { consoleList: null, + savedConsoleList: null, activeConsoleId: null, workspaceTabList: null, - createConsoleLoading: false + createConsoleLoading: false, + }; -export const getSavedConsoleList = () => { +export const getOpenConsoleList = () => { historyService - .getSavedConsoleList({ + .getConsoleList({ tabOpened: 'y', pageNo: 1, pageSize: 20, @@ -30,6 +33,18 @@ export const getSavedConsoleList = () => { }); }; +export const getSavedConsoleList = () => { + historyService + .getConsoleList({ + pageNo: 1, + pageSize: 100, + status: ConsoleStatus.RELEASE, + }) + .then((res) => { + useWorkspaceStore.setState({ savedConsoleList: res?.data }); + }); +} + export const setActiveConsoleId = (id: IConsoleStore['activeConsoleId']) => { useWorkspaceStore.setState({ activeConsoleId: id }); }; diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index 5f1efd7fc..82a55761e 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -89,7 +89,7 @@ const getWindowTab = createRequest<{ id: number, orderByDesc: boolean }, number> const updateSavedConsole = createRequest & {id: number}, number>('/api/operation/saved/update', { method: 'post' }); -const getSavedConsoleList = createRequest>('/api/operation/saved/list', {}); +const getConsoleList = createRequest>('/api/operation/saved/list', {}); const deleteSavedConsole = createRequest<{ id: number }, string>('/api/operation/saved/:id', { method: 'delete' }); @@ -98,7 +98,7 @@ const createHistory = createRequest('/api/operation/log/cr const getHistoryList = createRequest>('/api/operation/log/list', {}); export default { - getSavedConsoleList, + getConsoleList, updateSavedConsole, getHistoryList, createConsole, From e8e200eb57b7baf2af580cc40ec1e79a9b797a2f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 5 Dec 2023 10:39:35 +0800 Subject: [PATCH 110/126] MONGODB --- .../blocks/Tree/hooks/useGetRightClickMenu.ts | 1 + .../ConnectionEdit/config/dataSource.ts | 5 +-- .../components/ConnectionEdit/config/types.ts | 1 - .../components/OperationLine/index.tsx | 42 +++++++++++++------ .../components/TableBox/index.tsx | 19 ++++----- .../src/components/SearchResult/index.tsx | 14 ++++--- chat2db-client/src/constants/database.ts | 14 +++---- .../workspace/components/SaveList/index.tsx | 1 - .../components/WorkspaceTabs/index.tsx | 2 +- chat2db-client/src/service/sql.ts | 5 ++- 10 files changed, 60 insertions(+), 44 deletions(-) diff --git a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts index 42f65a0cf..54fdae30a 100644 --- a/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts +++ b/chat2db-client/src/blocks/Tree/hooks/useGetRightClickMenu.ts @@ -242,6 +242,7 @@ export const useGetRightClickMenu = (props: IProps) => { databaseType: treeNodeData.extraParams!.databaseType!, databaseName: treeNodeData.extraParams?.databaseName, schemaName: treeNodeData.extraParams?.schemaName, + tableName: treeNodeData.name, sql: 'select * from ' + databaseName, }, }); diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index 6289907d9..115aa9403 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -2070,11 +2070,10 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, template: 'mongodb://{host}:{port}/{database}', + excludes: [OperationColumn.ViewDDL, OperationColumn.CreateTable,OperationColumn.EditTable] }, ssh: sshConfig, - extendInfo: [ - - ], + extendInfo: [], type: DatabaseTypeCode.MONGODB }, ]; diff --git a/chat2db-client/src/components/ConnectionEdit/config/types.ts b/chat2db-client/src/components/ConnectionEdit/config/types.ts index 25d7c1b4d..2b542f8f4 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/types.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/types.ts @@ -39,7 +39,6 @@ export type IConnectionConfig = { pattern: RegExp; template: string; excludes?: OperationColumn[]; - }, driver?: { items: IFormItem[]; diff --git a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx index cf47b335a..479f845a3 100644 --- a/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx +++ b/chat2db-client/src/components/ConsoleEditor/components/OperationLine/index.tsx @@ -1,11 +1,12 @@ import React from 'react'; import i18n from '@/i18n'; -import { Button } from 'antd'; +import { Button, Popover } from 'antd'; import { IBoundInfo } from '@/typings/workspace'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import SelectBoundInfo from '../SelectBoundInfo'; import { formatSql } from '@/utils/sql'; +import { osNow } from '@/utils'; interface IProps { boundInfo: IBoundInfo; @@ -16,6 +17,19 @@ interface IProps { hasSaveBtn: boolean; } +const keyboardKey = (function () { + if (osNow().isMac) { + return { + command: 'Cmd', + Shift: 'Shift', + }; + } + return { + command: 'Ctrl', + Shift: 'Shift', + }; +})(); + const OperationLine = (props: IProps) => { const { boundInfo, saveConsole, editorRef, hasSaveBtn, executeSQL, setBoundInfo } = props; @@ -37,18 +51,22 @@ const OperationLine = (props: IProps) => { return (
- - {hasSaveBtn && ( - + + {hasSaveBtn && ( + + + )}